diff options
| author | Jan-Hendrik Willms <tleilax+github@gmail.com> | 2021-07-22 16:07:19 +0200 |
|---|---|---|
| committer | Jan-Hendrik Willms <tleilax+github@gmail.com> | 2021-07-22 16:19:12 +0200 |
| commit | a3da1483a9e689846179159355badfec8073dbec (patch) | |
| tree | 770dcca6bdf5f6f2a11b0e7fcbbeda6919a3fc52 /cli | |
current code from svn, revision 62608
Diffstat (limited to 'cli')
32 files changed, 3507 insertions, 0 deletions
diff --git a/cli/antelope_to_barracuda.php b/cli/antelope_to_barracuda.php new file mode 100755 index 0000000..4360ffd --- /dev/null +++ b/cli/antelope_to_barracuda.php @@ -0,0 +1,107 @@ +#!/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 new file mode 100755 index 0000000..e3cad62 --- /dev/null +++ b/cli/biest7783-fix.php @@ -0,0 +1,137 @@ +#!/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 new file mode 100755 index 0000000..e94a9f3 --- /dev/null +++ b/cli/biest7789-fix.php @@ -0,0 +1,95 @@ +#!/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 new file mode 100755 index 0000000..ca1f90c --- /dev/null +++ b/cli/biest7866-fix.php @@ -0,0 +1,47 @@ +#!/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 new file mode 100755 index 0000000..c1bad9a --- /dev/null +++ b/cli/biest8136-fix.php @@ -0,0 +1,31 @@ +#!/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 new file mode 100755 index 0000000..e028621 --- /dev/null +++ b/cli/check-help-tours.php @@ -0,0 +1,80 @@ +#!/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 new file mode 100755 index 0000000..49998ff --- /dev/null +++ b/cli/cleanup_admission_rules.php @@ -0,0 +1,49 @@ +#!/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 new file mode 100644 index 0000000..70475b8 --- /dev/null +++ b/cli/compatibility-rules/studip-4.0.php @@ -0,0 +1,179 @@ +<?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 new file mode 100644 index 0000000..4420e9f --- /dev/null +++ b/cli/compatibility-rules/studip-4.2.php @@ -0,0 +1,17 @@ +<?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 new file mode 100644 index 0000000..48e5165 --- /dev/null +++ b/cli/compatibility-rules/studip-4.4.php @@ -0,0 +1,6 @@ +<?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 new file mode 100644 index 0000000..af8a70e --- /dev/null +++ b/cli/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' => '#{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 new file mode 100755 index 0000000..a7f557e --- /dev/null +++ b/cli/create_table_schemes.php @@ -0,0 +1,47 @@ +#!/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 new file mode 100755 index 0000000..15f6e2e --- /dev/null +++ b/cli/cronjob-worker.php @@ -0,0 +1,34 @@ +#!/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 new file mode 100755 index 0000000..217c664 --- /dev/null +++ b/cli/cronjobs.php @@ -0,0 +1,54 @@ +#!/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 new file mode 100755 index 0000000..ebe0db3 --- /dev/null +++ b/cli/describe_models.php @@ -0,0 +1,75 @@ +#!/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 new file mode 100755 index 0000000..52ce0c1 --- /dev/null +++ b/cli/dump_studip.php @@ -0,0 +1,87 @@ +#!/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 new file mode 100755 index 0000000..70d14a9 --- /dev/null +++ b/cli/extract-js-localizations.php @@ -0,0 +1,204 @@ +#!/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_collate.php b/cli/fix_collate.php new file mode 100755 index 0000000..b9b9f67 --- /dev/null +++ b/cli/fix_collate.php @@ -0,0 +1,24 @@ +#!/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 new file mode 100755 index 0000000..7e31f29 --- /dev/null +++ b/cli/fix_endtime_weekly_recurred_events.php @@ -0,0 +1,33 @@ +#!/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 new file mode 100644 index 0000000..dde439f --- /dev/null +++ b/cli/getopts.php @@ -0,0 +1,317 @@ +<?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 new file mode 100755 index 0000000..6323eae --- /dev/null +++ b/cli/help-translation-tool.php @@ -0,0 +1,543 @@ +#!/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 new file mode 100755 index 0000000..e301413 --- /dev/null +++ b/cli/i18n-plugin.php @@ -0,0 +1,89 @@ +#!/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 new file mode 100755 index 0000000..7798fa4 --- /dev/null +++ b/cli/kill_studip_user.php @@ -0,0 +1,95 @@ +#!/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 new file mode 100755 index 0000000..2b43b17 --- /dev/null +++ b/cli/migrate.php @@ -0,0 +1,76 @@ +#!/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('1:d:lm:t:v'); + if ($options === false) { + exit(1); + } + + # check for options + $single = false; + $domain = 'studip'; + $list = false; + $path = $STUDIP_BASE_PATH . '/db/migrations'; + $verbose = false; + $target = null; + + foreach ($options as $option => $value) { + switch ($option) { + case '1': + $single = (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); + $migrator = new Migrator($path, $version, $verbose); + + if ($list) { + $migrations = $migrator->relevantMigrations($target); + + foreach ($migrations as $number => $migration) { + $description = $migration->description() ?: '(no description)'; + printf("%3d %s\n", $number, $description); + } + } elseif ($single) { + $direction = 'up'; + if ($single[0] === '-') { + $direction = 'down'; + $single = substr($single, 1); + } + $migrator->execute($single, $direction); + } else { + $migrator->migrateTo($target); + } +} diff --git a/cli/migrate_help_content.php b/cli/migrate_help_content.php new file mode 100755 index 0000000..71b4c66 --- /dev/null +++ b/cli/migrate_help_content.php @@ -0,0 +1,89 @@ +#!/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 new file mode 100755 index 0000000..85dab42 --- /dev/null +++ b/cli/myisam_to_innodb.php @@ -0,0 +1,122 @@ +#!/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 new file mode 100755 index 0000000..9a065fe --- /dev/null +++ b/cli/plugin_manager @@ -0,0 +1,306 @@ +#!/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] [-t] [-v]' . "\n"; + exit(1); + } + + // parse options + list($errors, $options, $args) = getopts(array('l' => 'Ss l list', 'v' => 'Ss v verbose', 't'=> 'Vs t target')); + $list = false; + $verbose = false; + $target = NULL; + + foreach ($options as $option => $value) { + switch ($option) { + 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']); + $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("%3d %-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->migrate_to(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-compat.php b/cli/studip-compat.php new file mode 100755 index 0000000..4abc67c --- /dev/null +++ b/cli/studip-compat.php @@ -0,0 +1,203 @@ +#!/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__ . '/compatbility-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 new file mode 100644 index 0000000..a0e33cd --- /dev/null +++ b/cli/studip_cli_env.inc.php @@ -0,0 +1,80 @@ +<?php +# Lifter007: TODO +# Lifter003: TODO +/** +* studip_cli_env.inc.php +* +* sets up a faked Stud.IP environment with usable $auth, $user and $perm objects +* for a faked 'root' user, sets custom error handler wich writes to STDERR +* +* @author André Noack <noack@data-quest.de>, Suchi & Berg GmbH <info@data-quest.de> +* @access public +*/ +// +---------------------------------------------------------------------------+ +// This file is part of Stud.IP +// studip_cli_env.inc.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. +// +---------------------------------------------------------------------------+ + +function CliErrorHandler($errno, $errstr, $errfile, $errline) { + if ($errno & ~(E_NOTICE | E_STRICT | E_DEPRECATED | E_WARNING | E_USER_WARNING | E_USER_NOTICE | E_USER_DEPRECATED) && error_reporting()){ + fwrite(STDERR,"$errstr \n$errfile line $errline\n"); + exit(1); + } + return true; +} + +function parse_msg_to_clean_text($long_msg,$separator="§") { + $msg = explode ($separator,$long_msg); + $ret = []; + for ($i=0; $i < count($msg); $i=$i+2) { + if ($msg[$i+1]) $ret[] = trim(decodeHTML(preg_replace ("'<[\/\!]*?[^<>]*?>'si", "", $msg[$i+1]))); + } + return join("\n", $ret); +} + +$STUDIP_BASE_PATH = realpath( dirname(__FILE__) . '/..'); +$include_path = get_include_path(); +$include_path .= PATH_SEPARATOR . $STUDIP_BASE_PATH . DIRECTORY_SEPARATOR . 'public'; +set_include_path($include_path); +set_error_handler('CliErrorHandler'); + +require_once $STUDIP_BASE_PATH . "/lib/bootstrap.php"; + +// disable caching for cli scripts +$GLOBAL_CACHING_ENABLE = $GLOBALS['CACHING_ENABLE']; +$CACHING_ENABLE = false; + +// set base url for URLHelper class +URLHelper::setBaseUrl($ABSOLUTE_URI_STUDIP); + +//cli scripts run always as faked (Stud.IP) root +$auth = new Seminar_Auth(); +$auth->auth = ['uid' => 'cli', + 'uname' => 'cli', + 'perm' => 'root']; + +$faked_root = new User(); +$faked_root->user_id = 'cli'; +$faked_root->username = 'cli'; +$faked_root->perms = 'root'; +$user = new Seminar_User($faked_root); +unset($faked_root); + +$perm = new Seminar_Perm(); +?> diff --git a/cli/tic_5671_scan.php b/cli/tic_5671_scan.php new file mode 100755 index 0000000..07fa5c2 --- /dev/null +++ b/cli/tic_5671_scan.php @@ -0,0 +1,172 @@ +#!/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 new file mode 100644 index 0000000..50c6e4b --- /dev/null +++ b/cli/update-resource-booking-intervals.php @@ -0,0 +1,33 @@ +#!/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 new file mode 100755 index 0000000..f2b2ec8 --- /dev/null +++ b/cli/vue-gettext-split-translations.php @@ -0,0 +1,16 @@ +#!/usr/bin/env php +<?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)); +} |
