<?php # Lifter002: DONE - not applicable # Lifter003: TEST # Lifter007: TODO # Lifter010: DONE - not applicable /** * functions.php * * The Stud.IP-Core functions. Look to the descriptions to get further details * * 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 Cornelis Kater <ckater@gwdg.de> * @author Suchi & Berg GmbH <info@data-quest.de> * @author Ralf Stockmann <rstockm@gwdg.de> * @author André Noack <andre.noack@gmx.net> * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 * @category Stud.IP * @access public * @package studip_cores * @modulegroup library * @module functions.php */ // +---------------------------------------------------------------------------+ // This file is part of Stud.IP // functions.php // Stud.IP Kernfunktionen // Copyright (C) 2002 Cornelis Kater <ckater@gwdg.de>, Suchi & Berg GmbH <info@data-quest.de>, // Ralf Stockmann <rstockm@gwdg.de>, André Noack André Noack <andre.noack@gmx.net> // +---------------------------------------------------------------------------+ // 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 'lib/object.inc.php'; require_once 'lib/user_visible.inc.php'; /** * returns an array containing name and type of the passed objeact * denoted by $range_id * * @global array $SEM_TYPE * @global array $INST_TYPE * @global array $SEM_TYPE_MISC_NAME * * @param string $range_id the id of the object * @param string $object_type the type of the object * * @return array an array containing name and type of the object */ function get_object_name($range_id, $object_type) { global $SEM_TYPE,$INST_TYPE, $SEM_TYPE_MISC_NAME; $name = ''; $type = ''; if ($object_type == "sem") { $query = "SELECT status, Name FROM seminare WHERE Seminar_id = ?"; $statement = DBManager::get()->prepare($query); $statement->execute([$range_id]); $row = $statement->fetch(PDO::FETCH_ASSOC); if (!$row) { $name = _('Unbekannt'); $type = _('Veranstaltung'); } else { $name = $row['Name']; $type = $SEM_TYPE[$row['status']]['name'] ?? _('Veranstaltung'); if ($type === $SEM_TYPE_MISC_NAME) { $type = _('Veranstaltung'); } } } else if ($object_type == 'inst' || $object_type == 'fak') { $query = "SELECT type, Name FROM Institute WHERE Institut_id = ?"; $statement = DBManager::get()->prepare($query); $statement->execute([$range_id]); $row = $statement->fetch(PDO::FETCH_ASSOC); if (!$row) { $name = _('Unbekannt'); $type = _('Einrichtung'); } else { $name = $row['Name']; $type = $INST_TYPE[$row['type']]['name'] ?? _('Einrichtung'); } } return compact('name', 'type'); } /** * Returns a sorm object for a given range_id * * @param string the range_id * @return bool|SimpleORMap Course/Institute/User/Statusgruppen/ */ function get_object_by_range_id($range_id) { $possible_sorms = "Course Institute User"; foreach(words($possible_sorms) as $sorm) { if ($object = $sorm::find($range_id)) { return $object; } } return false; } /** * This function checks, if there is an open Veranstaltung or Einrichtung * * @throws CheckObjectException * * @return void */ function checkObject() { if (!Context::get()) { throw new CheckObjectException(_('Sie haben kein Objekt gewählt.')); } } /** * This function checks, if given module * is allowed for this stud.ip-object. * * @throws CheckObjectException * @param string $module the module to check for * @param bool $is_plugin_name is the module name old style ( "wiki","scm") or new style (the name of the plugin) * @return StudipModule */ function checkObjectModule($module, $is_plugin_name = false) { if ($context = Context::get()) { if (!$is_plugin_name) { $module_name = "Core" . ucfirst($module); } else { $module_name = $module; } $studip_module = PluginManager::getInstance()->getPlugin($module_name); if (!$studip_module || !$studip_module->isActivated($context->getId())) { throw new CheckObjectException(sprintf(_('Das Inhaltselement "%s" ist für dieses Objekt leider nicht verfügbar.'), ucfirst($module))); } return $studip_module; } } /** * This function closes a opened Veranstaltung or Einrichtung * * @return void */ function closeObject() { Context::close(); } /** * This function determines the type of the passed id * * The function recognizes the following types at the moment: * Einrichtungen, Veranstaltungen, Statusgruppen and Fakultaeten * * @staticvar array $object_type_cache * * @param string $id the id of the object * @param array $check_only an array to narrow the search, may contain * 'sem', 'inst', 'fak', 'group' or 'dokument' (optional) * * @return string return "inst" (Einrichtung), "sem" (Veranstaltung), * "fak" (Fakultaeten), "group" (Statusgruppe), "dokument" (Dateien) * */ function get_object_type($id, $check_only = []) { static $cache = null; // Nothing to check if (!$id) { return false; } // Id is global if ($id === 'studip') { return 'global'; } // Initialize cache array if ($cache === null) { $cache = new StudipCachedArray('Studip/ObjectTypes'); } // No cached entry available? Go ahead and determine type if (!isset($cache[$id])) { // Tests for specific types $tests = [ "SELECT 'sem' FROM `seminare` WHERE `Seminar_id` = ?" => ['sem'], "SELECT IF(`Institut_id` = `fakultaets_id`, 'fak', 'inst') FROM `Institute` WHERE `Institut_id` = ?" => ['inst', 'fak'], "SELECT 'date' FROM `termine` WHERE `termin_id` = ?" => ['date'], "SELECT 'user' FROM `auth_user_md5` WHERE `user_id` = ?" => ['user'], "SELECT 'group' FROM `statusgruppen` WHERE `statusgruppe_id` = ?" => ['group'], "SELECT 'dokument' FROM `file_refs` WHERE `id` = ?" => ['dokument'], "SELECT 'range_tree' FROM `range_tree` WHERE `item_id` = ?" => ['range_tree'], ]; // If we want to check only for a specific type, order the tests so that // these tests will be executed first if ($check_only) { uasort($tests, function ($a, $b) use ($check_only) { return count(array_intersect($b, $check_only)) - count(array_intersect($a, $check_only)); }); } // Actually determine type $type = null; foreach ($tests as $query => $types) { $type = DBManager::get()->fetchColumn($query, [$id]); if ($type) { break; } } // Store type $cache[$id] = $type ?? false; } return (!$check_only || in_array($cache[$id], $check_only)) ? $cache[$id] : false; } /** * This function calculates one of the group colors unique for the semester of * the passed timestamp * * It calculates a unique color number to create the initial entry for a new user in a seminar. * It will create a unique number for every semester and will start over, if the max. number * (7) is reached. * * @param integer $sem_start_time the timestamp of the start time from the Semester * * @return integer the color number * */ function select_group($sem_start_time) { //Farben Algorhytmus, erzeugt eindeutige Farbe fuer jedes Semester. Funktioniert ab 2001 die naechsten 1000 Jahre..... $year_of_millenium = date ('Y', $sem_start_time) % 1000; $index = $year_of_millenium * 2; if (date('n', $sem_start_time) > 6) $index++; return ($index % 7) + 1; } /** * The function shortens a string, but it uses the first 2/3 and the last 1/3 * * The parts will be divided by a "[...]". The functions is to use like php's * mb_substr function. * * @param string $what the original string * @param integer $start start pos, 0 is the first pos * @param integer $end end pos * * @return string * * */ function my_substr($what, $start, $end) { $length = $end - $start; $what_length = mb_strlen($what); // adding 5 because: mb_strlen("[...]") == 5 if ($what_length > $length + 5) { $what = mb_substr($what, $start, round(($length / 3) * 2)) . "[...]" . mb_substr($what, $what_length - round($length / 3), $what_length); } return $what; } /** * Retrieves the fullname for a given user_id * * @param string $user_id if omitted, current user_id is used * @param string $format output format * @param bool $htmlready if true, htmlReady is applied to all output-strings * * @return string */ function get_fullname($user_id = "", $format = "full" , $htmlready = false) { static $cache = []; $current_user = User::findCurrent(); if (!$user_id) { $user_id = $current_user ? $current_user->id : null; } if ($current_user && $current_user->id === $user_id) { $fullname = $current_user->getFullName($format); return $htmlready ? htmlReady($fullname) : $fullname; } $hash = md5($user_id . $format); if (!isset($cache[$hash])) { $query = "SELECT {$GLOBALS['_fullname_sql'][$format]} FROM auth_user_md5 LEFT JOIN user_info USING (user_id) WHERE user_id = ?"; $statement = DBManager::get()->prepare($query); $statement->execute([$user_id]); $cache[$hash] = $statement->fetchColumn() ?: _('unbekannt'); } return $htmlready ? htmlReady($cache[$hash]) : $cache[$hash]; } /** * Retrieves the fullname for a given username * * @param string $uname if omitted, current user_id is used * @param string $format output format * @param bool $htmlready if true, htmlReady is applied to all output-strings * * @return string */ function get_fullname_from_uname($uname = "", $format = "full", $htmlready = false) { static $cache; global $auth, $_fullname_sql; if (!$uname) { $uname = $auth->auth['uname']; } $hash = md5($uname . $format); if (!isset($cache[$hash])) { $query = "SELECT {$_fullname_sql[$format]} FROM auth_user_md5 LEFT JOIN user_info USING (user_id) WHERE username = ?"; $statement = DBManager::get()->prepare($query); $statement->execute([$uname]); $cache[$hash] = $statement->fetchColumn() ?: _('unbekannt'); } return $htmlready ? htmlReady($cache[$hash]) : $cache[$hash]; } /** * Retrieves the username for a given user_id * * @global object $auth * @staticvar array $cache * * @param string $user_id if omitted, current username will be returned * * @return string * */ function get_username($user_id = "") { static $cache = []; global $auth; if (!$user_id || $user_id === $auth->auth['uid']) { return $auth->auth['uname'] ?? ''; } if (!isset($cache[$user_id])) { $query = "SELECT username FROM auth_user_md5 WHERE user_id = ?"; $statement = DBManager::get()->prepare($query); $statement->execute([$user_id]); $cache[$user_id] = $statement->fetchColumn(); } return $cache[$user_id]; } /** * Retrieves the userid for a given username * * uses global $online array if user is online * * @global object $auth * @staticvar array $cache * * @param string $username if omitted, current user_id will be returned * * @return string */ function get_userid($username = "") { static $cache = []; global $auth; if (!$username || $username == $auth->auth['uname']) { return $auth->auth['uid']; } // Read id from database if no cached version is available if (!isset($cache[$username])) { $query = "SELECT user_id FROM auth_user_md5 WHERE username = ?"; $statement = DBManager::get()->prepare($query); $statement->execute([$username]); $cache[$username] = $statement->fetchColumn(); } return $cache[$username]; } /** * Return an array containing the nodes of the sem-tree-path * * @param string $seminar_id the seminar to get the path for * @param int $depth the depth * @param string $delimeter a string to separate the path parts * * @return array */ function get_sem_tree_path($seminar_id, $depth = false, $delimeter = ">") { $the_tree = TreeAbstract::GetInstance("StudipSemTree"); $view = DbView::getView('sem_tree'); $ret = []; $view->params[0] = $seminar_id; $rs = $view->get_query("view:SEMINAR_SEM_TREE_GET_IDS"); while ($rs->next_record()){ $ret[$rs->f('sem_tree_id')] = $the_tree->getShortPath($rs->f('sem_tree_id'), null, $delimeter, $depth ? $depth - 1 : 0); } return $ret; } /** * check_and_set_date * * Checks if given date is valid and sets field in array accordingly. * (E.g. $admin_admission_data['admission_enddate']) * * @param mixed $tag day or placeholder for day * @param mixed $monat month or placeholder for month * @param mixed $jahr year or placeholder for year * @param mixed $stunde hours or placeholder for hours * @param mixed $minute minutes or placeholder for minutes * @param array &$arr Reference to array to update. If NULL, only check is performed * @param mixed $field Name of field in array to be set * * @return bool true if date was valid, false else */ function check_and_set_date($tag, $monat, $jahr, $stunde, $minute, &$arr, $field) { $check = true; // everything ok? if (($jahr>0) && ($jahr<100)) $jahr=$jahr+2000; if ($monat == _("mm")) $monat=0; if ($tag == _("tt")) $tag=0; if ($jahr == _("jjjj")) $jahr=0; //if ($stunde == _("hh")) $stunde=0; if ($minute == _("mm")) $minute=0; if (($monat) && ($tag) && ($jahr)) { if ($stunde==_("hh")) { $check = false; } if ((!checkdate((int)$monat, (int)$tag, (int)$jahr) && ((int)$monat) && ((int)$tag) && ((int)$jahr))) { $check = false; } if (($stunde > 24) || ($minute > 59) || ($stunde == 24 && $minute > 0) ) { $check = false; } if ($stunde == 24) { $stunde = 23; $minute = 59; } if ($arr) { if ($check) { $arr[$field] = mktime((int)$stunde,(int)$minute, 0,$monat,$tag,$jahr); } else { $arr[$field] = -1; } } } return $check; } /** * converts a string to a float, depending on the locale * * @param string $str the string to convert to float * * @return float the string casted to float */ function StringToFloat($str) { $str = mb_substr((string)$str,0,13); $locale = localeconv(); $from = ($locale["thousands_sep"] ? $locale["thousands_sep"] : ','); $to = ($locale["decimal_point"] ? $locale["decimal_point"] : '.'); if(mb_strstr($str, $from)){ $conv_str = str_replace($from, $to, $str); $my_float = (float)$conv_str; if ($conv_str === (string)$my_float) return $my_float; } return (float)$str; } /** * check which perms the currently logged in user had in the * passed archived seminar * * @global array $perm * @global object $auth * @staticvar array $archiv_perms * * @param string $seminar_id the seminar in the archive * * @return string the perm the user had */ function archiv_check_perm($seminar_id) { static $archiv_perms; $u_id = $GLOBALS['user']->id; // root darf sowieso ueberall dran if ($GLOBALS['perm']->have_perm('root')) { return 'admin'; } if (!is_array($archiv_perms)){ $query = "SELECT seminar_id, status FROM archiv_user WHERE user_id = ?"; $statement = DBManager::get()->prepare($query); $statement->execute([$u_id]); $archiv_perms = $statement->fetchGrouped(PDO::FETCH_COLUMN); if ($GLOBALS['perm']->have_perm('admin')) { $query = "SELECT archiv.seminar_id, 'admin' FROM user_inst INNER JOIN archiv ON (heimat_inst_id = institut_id) WHERE user_inst.user_id = ? AND user_inst.inst_perms = 'admin'"; $statement = DBManager::get()->prepare($query); $statement->execute([$u_id]); $temp_perms = $statement->fetchGrouped(PDO::FETCH_COLUMN); $archiv_perms = array_merge($archiv_perms, $temp_perms); } if ($GLOBALS['perm']->is_fak_admin()) { $query = "SELECT archiv.seminar_id, 'admin' FROM user_inst INNER JOIN Institute ON (user_inst.institut_id = Institute.fakultaets_id) INNER JOIN archiv ON (archiv.heimat_inst_id = Institute.institut_id) WHERE user_inst.user_id = ? AND user_inst.inst_perms = 'admin'"; $statement = DBManager::get()->prepare($query); $statement->execute([$u_id]); $temp_perms = $statement->fetchGrouped(PDO::FETCH_COLUMN); $archiv_perms = array_merge($archiv_perms, $temp_perms); } } return $archiv_perms[$seminar_id] ?? ''; } /** * retrieve a list of all online users * * @global object $user * @global array $_fullname_sql * * @param int $active_time filter: the time in minutes until last life-sign * @param string $name_format format the fullname shall have * * @return array */ function get_users_online($active_time = 5, $name_format = 'full_rev') { if (!isset($GLOBALS['_fullname_sql'][$name_format])) { $sql_fullname = array_keys($GLOBALS['_fullname_sql']); $name_format = reset($sql_fullname); } $query = "SELECT a.username AS temp, a.username, {$GLOBALS['_fullname_sql'][$name_format]} AS name, ABS(CAST(UNIX_TIMESTAMP() AS SIGNED) - CAST(last_lifesign AS SIGNED)) AS last_action, a.user_id, IF(owner_id IS NOT NULL, 1, 0) AS is_buddy, " . get_vis_query('a', 'online') . " AS is_visible, a.visible FROM user_online uo JOIN auth_user_md5 a ON (a.user_id = uo.user_id) LEFT JOIN user_info ON (user_info.user_id = uo.user_id) LEFT JOIN user_visibility ON (user_visibility.user_id = uo.user_id) LEFT JOIN contact ON (owner_id = ? AND contact.user_id = a.user_id) WHERE last_lifesign > ? AND uo.user_id <> ? ORDER BY {$GLOBALS['_fullname_sql'][$name_format]} ASC"; $statement = DBManager::get()->prepare($query); $statement->execute([ $GLOBALS['user']->id, time() - $active_time * 60, $GLOBALS['user']->id, ]); $online = $statement->fetchGrouped(); // measure users online if ($active_time === 10) { Metrics::gauge('core.users_online', sizeof($online)); } return $online; } /** * get the number of currently online users * * @param int $active_time filter: the time in minutes until last life-sign * * @return int */ function get_users_online_count($active_time = 10) { $cache = StudipCacheFactory::getCache(); $online_count = $cache->read("online_count/{$active_time}"); if ($online_count === false) { $query = "SELECT COUNT(*) FROM user_online WHERE last_lifesign > ?"; $statement = DBManager::get()->prepare($query); $statement->execute([time() - $active_time * 60]); $online_count = $statement->fetchColumn(); $cache->write("online_count/{$active_time}", $online_count, 180); } if ($GLOBALS['user']->id && $GLOBALS['user']->id !== 'nobody') { --$online_count; } return $online_count > 0 ? $online_count : 0; } /** * return a studip-ticket * * @return string a unique id referring to a newly created ticket */ function get_ticket() { return Seminar_Session::get_ticket(); } /** * check if the passed ticket is valid * * @param string $studipticket the ticket-id to check * * @return bool */ function check_ticket($studipticket) { return Seminar_Session::check_ticket($studipticket); } /** * searches * * @global array $perm * @global object $user * @global array $_fullname_sql * * @param string $search_str optional search-string * @param string $search_user optional user to search for * @param bool $show_sem if true, the seminar is added to the result * * @return array */ function search_range($search_str = false, $search_user = false, $show_sem = true) { global $_fullname_sql; // Helper function that obtains the correct name for an entity taking // in account whether the semesters should be displayed or not $formatName = function ($row) use ($show_sem) { $name = $row['Name']; if ($show_sem) { $name = sprintf('%s (%s%s)', $name, $row['startsem'], $row['startsem'] != $row['endsem'] ? ' - ' . $row['endsem'] : ''); } return $name; }; $search_result = []; $show_sem_sql1 = ", s.start_time, (SELECT semester_data.name FROM semester_data WHERE s.start_time >= semester_data.`beginn` AND s.start_time <= semester_data.`ende` LIMIT 1) AS startsem, IF(semester_courses.semester_id IS NULL, '"._("unbegrenzt")."', (SELECT semester_data.name FROM semester_data LEFT JOIN semester_courses USING (semester_id) WHERE semester_courses.course_id = s.Seminar_id ORDER BY semester_data.`beginn` DESC LIMIT 1)) AS endsem "; $show_sem_sql2 = "LEFT JOIN semester_courses ON (semester_courses.course_id = s.Seminar_id) "; if ($search_str && $GLOBALS['perm']->have_perm('root')) { if ($search_user) { $query = "SELECT user_id, CONCAT({$_fullname_sql['full']}, ' (', username, ')') AS name FROM auth_user_md5 AS a LEFT JOIN user_info USING (user_id) WHERE CONCAT(Vorname, ' ', Nachname, ' ', username) LIKE CONCAT('%', ?, '%') ORDER BY Nachname, Vorname"; $statement = DBManager::get()->prepare($query); $statement->execute([$search_str]); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { $search_result[$row['user_id']] = [ 'type' => 'user', 'name' => $row['name'], ]; } } $_hidden = _('(versteckt)'); $query = "SELECT Seminar_id, IF(s.visible = 0, CONCAT(s.Name, ' {$_hidden}'), s.Name) AS Name %s FROM seminare AS s %s WHERE s.Name LIKE CONCAT('%%', ?, '%%') GROUP BY s.Seminar_id ORDER BY start_time DESC, Name"; $query = $show_sem ? sprintf($query, $show_sem_sql1, $show_sem_sql2) : sprintf($query, '', ''); $statement = DBManager::get()->prepare($query); $statement->execute([$search_str]); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { $search_result[$row['Seminar_id']] = [ 'type' => 'sem', 'name' => $formatName($row), 'starttime' => $row['start_time'], 'startsem' => $row['startsem'], ]; } $query = "SELECT Institut_id, Name, IF(Institut_id = fakultaets_id, 'fak', 'inst') AS type FROM Institute WHERE Name LIKE CONCAT('%', ?, '%') ORDER BY Name"; $statement = DBManager::get()->prepare($query); $statement->execute([$search_str]); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { $search_result[$row['Institut_id']] = [ 'type' => $row['type'], 'name' => $row['Name'], ]; } } elseif ($search_str && $GLOBALS['perm']->have_perm('admin')) { $_hidden = _('(versteckt)'); $query = "SELECT s.Seminar_id, IF(s.visible = 0, CONCAT(s.Name, ' {$_hidden}'), s.Name) AS Name %s FROM user_inst AS a JOIN seminare AS s USING (Institut_id) %s WHERE a.user_id = ? AND a.inst_perms = 'admin' AND s.Name LIKE CONCAT('%%', ?, '%%') ORDER BY start_time"; $query = $show_sem ? sprintf($query, $show_sem_sql1, $show_sem_sql2) : sprintf($query, '', ''); $statement = DBManager::get()->prepare($query); $statement->execute([$GLOBALS['user']->id, $search_str]); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { $search_result[$row['Seminar_id']] = [ 'type' => 'sem', 'name' => $formatName($row), 'starttime' => $row['start_time'], 'startsem' => $row['startsem'], ]; } $query = "SELECT b.Institut_id, b.Name FROM user_inst AS a JOIN Institute AS b USING (Institut_id) WHERE a.user_id = ? AND a.inst_perms = 'admin' AND a.institut_id != b.fakultaets_id AND b.Name LIKE CONCAT('%', ?, '%') ORDER BY Name"; $statement = DBManager::get()->prepare($query); $statement->execute([$GLOBALS['user']->id, $search_str]); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { $search_result[$row['Institut_id']] = [ 'type' => 'inst', 'name' => $row['Name'], ]; } if ($GLOBALS['perm']->is_fak_admin()) { $_hidden = _('(versteckt)'); $query = "SELECT s.Seminar_id, IF(s.visible = 0, CONCAT(s.Name, ' {$_hidden}'), s.Name) AS Name %s FROM user_inst AS a JOIN Institute AS b ON (a.Institut_id = b.Institut_id AND b.Institut_id = b.fakultaets_id) JOIN Institute AS c ON (c.fakultaets_id = b.Institut_id AND c.fakultaets_id != c.Institut_id) JOIN seminare AS s ON (s.Institut_id = c.Institut_id) %s WHERE a.user_id = ? AND a.inst_perms = 'admin' AND s.Name LIKE CONCAT('%%', ?, '%%') ORDER BY start_time DESC, Name"; $query = $show_sem ? sprintf($query, $show_sem_sql1, $show_sem_sql2) : sprintf($query, '', ''); $statement = DBManager::get()->prepare($query); $statement->execute([$GLOBALS['user']->id, $search_str]); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { $search_result[$row['Seminar_id']] = [ 'type' => 'sem', 'name' => $formatName($row), 'starttime' => $row['start_time'], 'startsem' => $row['startsem'], ]; } $query = "SELECT c.Institut_id, c.Name FROM user_inst AS a JOIN Institute AS b ON (a.Institut_id = b.Institut_id AND b.Institut_id = b.fakultaets_id) JOIN Institute AS c ON (c.fakultaets_id = b.institut_id AND c.fakultaets_id != c.institut_id) WHERE a.user_id = ? AND a.inst_perms = 'admin' AND c.Name LIKE CONCAT('%', ?, '%') ORDER BY Name"; $statement = DBManager::get()->prepare($query); $statement->execute([$GLOBALS['user']->id, $search_str]); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { $search_result[$row['Institut_id']] = [ 'type' => 'inst', 'name' => $row['Name'], ]; } $query = "SELECT b.Institut_id, b.Name FROM user_inst AS a JOIN Institute AS b ON (a.Institut_id = b.Institut_id AND b.Institut_id = b.fakultaets_id) WHERE a.user_id = ? AND a.inst_perms = 'admin' AND b.Name LIKE CONCAT('%', ?, '%') ORDER BY Name"; $statement = DBManager::get()->prepare($query); $statement->execute([$GLOBALS['user']->id, $search_str]); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { $search_result[$row['Institut_id']] = [ 'type' => 'inst', 'name' => $row['Name'], ]; } } } elseif ($GLOBALS['perm']->have_perm('tutor') || $GLOBALS['perm']->have_perm('autor')) { // autors my also have evaluations and news in studygroups with proper rights $_hidden = _('(versteckt)'); $query = "SELECT s.Seminar_id, IF(s.visible = 0, CONCAT(s.Name, ' {$_hidden}'), s.Name) AS Name %s FROM seminar_user AS a JOIN seminare AS s USING (Seminar_id) %s WHERE a.user_id = ? AND a.status IN ('tutor', 'dozent') ORDER BY start_time DESC, Name"; $query = $show_sem ? sprintf($query, $show_sem_sql1, $show_sem_sql2) : sprintf($query, '', ''); $statement = DBManager::get()->prepare($query); $statement->execute([$GLOBALS['user']->id]); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { $search_result[$row['Seminar_id']] = [ 'type' => 'sem', 'name' => $formatName($row), 'starttime' => $row['start_time'], 'startsem' => $row['startsem'], ]; } $query = "SELECT Institut_id, b.Name, IF (Institut_id = fakultaets_id, 'fak', 'inst') AS type FROM user_inst AS a JOIN Institute AS b USING (Institut_id) WHERE a.user_id = ? AND a.inst_perms IN ('dozent','tutor') ORDER BY Name"; $statement = DBManager::get()->prepare($query); $statement->execute([$GLOBALS['user']->id]); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { $search_result[$row['Institut_id']] = [ 'name' => $row['Name'], 'type' => $row['type'], ]; } } if (Config::get()->DEPUTIES_ENABLE) { $_hidden = _('(versteckt)'); $_deputy = _('Vertretung'); $query = "SELECT s.Seminar_id, CONCAT(IF(s.visible = 0, CONCAT(s.Name, ' {$_hidden}'), s.Name), ' [{$_deputy}]') AS Name %s FROM seminare AS s JOIN deputies AS d ON (s.Seminar_id = d.range_id) %s WHERE d.user_id = ? ORDER BY s.start_time DESC, Name"; $query = $show_sem ? sprintf($query, $show_sem_sql1, $show_sem_sql2) : sprintf($query, '', ''); $statement = DBManager::get()->prepare($query); $statement->execute([$GLOBALS['user']->id]); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { $search_result[$row['Seminar_id']] = [ 'type' => 'sem', 'name' => $formatName($row), 'starttime' => $row['start_time'], 'startsem' => $row['startsem'], ]; } if (Deputy::isEditActivated()) { $query = "SELECT a.user_id, a.username, 'user' AS type, CONCAT({$_fullname_sql['full']}, ' (', username, ')') AS name FROM auth_user_md5 AS a JOIN user_info USING (user_id) JOIN deputies AS d ON (a.user_id = d.range_id) WHERE d.user_id = ? ORDER BY name ASC"; $statement = DBManager::get()->prepare($query); $statement->execute([ $GLOBALS['user']->id ]); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { $search_result[$row['user_id']] = $row; } } } return $search_result ?: null; } /** * format_help_url($keyword) * returns URL for given help keyword * * @param string $keyword the help-keyword * * @return string the help-url */ function format_help_url($keyword) { // all help urls need short language tag (de, en) $lang = 'de'; if (!empty($_SESSION['_language'])) { [$lang] = explode('_', $_SESSION['_language']); } // determine major Stud.IP version from SOFTWARE_VERSION. preg_match('/^\d+/', $GLOBALS['SOFTWARE_VERSION'], $v); $version = $v[0]; return sprintf('https://hilfe.studip.de/help/%s/%s/%s', $version, $lang, $keyword); } /** * Splits a string by space characters and returns these words as an array. * If an array is given, returns the array itself. * * @param mixed $what what to split * @return array the words of the string as array */ function words($what) { return is_array($what) ? $what : preg_split('/ /', $what, -1, PREG_SPLIT_NO_EMPTY); } /** * Encodes a string or array from UTF-8 to Stud.IP encoding (WINDOWS-1252/ISO-8859-1 with numeric HTML-ENTITIES) * * @param mixed $data a string in UTF-8 or an array with all strings encoded in utf-8 * * @return string the string in WINDOWS-1252/HTML-ENTITIES */ function legacy_studip_utf8decode($data) { if (is_array($data)) { $new_data = []; foreach ($data as $key => $value) { $key = legacy_studip_utf8decode($key); $new_data[$key] = legacy_studip_utf8decode($value); } return $new_data; } if (!preg_match('/[\200-\377]/', $data)) { return $data; } else { $windows1252 = [ "\x80" => '€', "\x81" => '�', "\x82" => '‚', "\x83" => 'ƒ', "\x84" => '„', "\x85" => '…', "\x86" => '†', "\x87" => '‡', "\x88" => 'ˆ', "\x89" => '‰', "\x8A" => 'Š', "\x8B" => '‹', "\x8C" => 'Œ', "\x8D" => '�', "\x8E" => 'Ž', "\x8F" => '�', "\x90" => '�', "\x91" => '‘', "\x92" => '’', "\x93" => '“', "\x94" => '”', "\x95" => '•', "\x96" => '–', "\x97" => '—', "\x98" => '˜', "\x99" => '™', "\x9A" => 'š', "\x9B" => '›', "\x9C" => 'œ', "\x9D" => '�', "\x9E" => 'ž', "\x9F" => 'Ÿ']; return str_replace( array_values($windows1252), array_keys($windows1252), utf8_decode(mb_encode_numericentity( $data, [0x100, 0xffff, 0, 0xffff], 'UTF-8' )) ); } } /** * Encode an HTTP header parameter (e.g. filename for 'Content-Disposition'). * * @param string $name parameter name * @param string $value parameter value * * @return string encoded header text (using RFC 2616 or 5987 encoding) */ function encode_header_parameter($name, $value) { if (preg_match('/[\200-\377]/', $value)) { // use RFC 5987 encoding (ext-parameter) return $name . "*=UTF-8''" . rawurlencode($value); } else { // use RFC 2616 encoding (quoted-string) return $name . '="' . addslashes($value) . '"'; } } /** * Get the title used for the given status ('dozent', 'tutor' etc.) for the * specified SEM_TYPE. Alternative titles can be defined in the config.inc.php. * * @global array $SEM_TYPE * @global array $DEFAULT_TITLE_FOR_STATUS * * @param string $type status ('dozent', 'tutor', 'autor', 'user' or 'accepted') * @param int $count count, this determines singular or plural form of title * @param int $sem_type sem_type of course (defaults to type of current course) * * @return string translated title for status */ function get_title_for_status($type, $count, $sem_type = NULL) { global $SEM_CLASS, $SEM_TYPE, $DEFAULT_TITLE_FOR_STATUS; if (is_null($sem_type)) { $sem_type = Context::getArtNum(); } $atype = 'title_'.$type; $index = $count == 1 ? 0 : 1; $class_index = $count == 1 ? $atype : $atype . '_plural'; return $SEM_CLASS[$SEM_TYPE[$sem_type]['class']][$class_index] ?? $DEFAULT_TITLE_FOR_STATUS[$type][$index] ?? _('unbekannt'); } /** * Test whether the given URL refers to some page or resource of * this Stud.IP installation. * * @param string $url url to check * * @return mixed */ function is_internal_url($url) { if (preg_match('%^[a-z]+:%', $url)) { return mb_strpos($url, $GLOBALS['ABSOLUTE_URI_STUDIP']) === 0; } if ($url[0] === '/') { return mb_strpos($url, $GLOBALS['CANONICAL_RELATIVE_PATH_STUDIP']) === 0; } return true; } /** * Return the list of SEM_TYPES that represent study groups in this * Stud.IP installation. * * @return array list of SEM_TYPES used for study groups */ function studygroup_sem_types() { $result = []; foreach ($GLOBALS['SEM_TYPE'] as $id => $sem_type) { if ($GLOBALS['SEM_CLASS'][$sem_type['class']]['studygroup_mode']) { $result[] = $id; } } return $result; } /** * generates form fields for the submitted multidimensional array * * @param string $variable the name of the array, which is filled with the data * @param mixed $data the data-array * @param mixed $parent leave this entry as is * * @return string the inputs of type hidden as html */ function addHiddenFields($variable, $data, $parent = []) { $ret = ""; if (is_array($data)) { foreach($data as $key => $value) { if (is_array($value)) { $ret .= addHiddenFields($variable, $value, array_merge($parent, [$key])); } else { $ret.= '<input type="hidden" name="'. htmlReady($variable .'['. implode('][', array_merge($parent, [$key])) .']').'" value="'. htmlReady($value) .'">' ."\n"; } } } else { $ret.= '<input type="hidden" name="'. htmlReady($variable) .'" value="'. htmlReady($data) .'">' ."\n"; } return $ret; } /** * Returns a new array that is a one-dimensional flattening of this * array (recursively). That is, for every element that is an array, * extract its elements into the new array. * * @param array $ary the array to be flattened * @return array the flattened array */ function array_flatten($ary) { $i = 0; while ($i < sizeof($ary)) { if (is_array($ary[$i])) { array_splice($ary, $i, 1, $ary[$i]); } else { $i++; } } return $ary; } /** * Displays "relative time" - a textual representation between now and a * certain timestamp, e.g. "3 hours ago". * * @param int $timestamp Timestamp to relate to. * @param bool $verbose Display long or short texts (optional) * @param int $displayed_levels How many levels shall be displayed * @param int $tolerance Defines a tolerance area of seconds around * now (How many seconds must have passed until * the function won't return "now") * @return String Textual representation of the difference between the passed * timestamp and now */ function reltime($timestamp, $verbose = true, $displayed_levels = 1, $tolerance = 5) { if ($verbose) { $glue = [', ', _(' und ')]; $levels = [ [60, _('%u Sekunde'), _('%u Sekunden')], [60, _('%u Minute'), _('%u Minuten')], [24, _('%u Stunde'), _('%u Stunden')], [30, _('%u Tag'), _('%u Tagen')], [12, _('%u Monat'), _('%u Monaten')], [99, _('%u Jahr'), _('%u Jahren')], ]; } else { $glue = ['', '']; $levels = [ [60, _('%us'), _('%us')], [60, _('%umin'), _('%umin')], [24, _('%uh'), _('%uh')], [30, _('%ud'), _('%ud')], [12, _('%uM'), _('%uM')], [99, _('%uy'), _('%uy')], ]; } $now = time(); $diff = abs($timestamp - $now); if ($diff < $tolerance) { return _('jetzt'); } $chunks = []; for ($i = 0; $i < count($levels) && $diff > 0; $i++) { $remainder = $diff % $levels[$i][0]; if ($remainder > 0) { $chunks[] = sprintf(ngettext($levels[$i][1], $levels[$i][2], $remainder), $remainder); } $diff = floor($diff / $levels[$i][0]); if ($diff === 0) { break; } } $chunks = array_reverse($chunks); $chunks = array_slice($chunks, 0, $displayed_levels); if (count($chunks) == 1) { $result = $chunks[0]; } else { $result = $chunks[0] . $glue[1] . implode($glue[0], array_slice($chunks, 1)); } if ($verbose) { $result = sprintf($timestamp < $now ? _('vor %s') : _('in %s'), $result); } return $result; } /** * Displays a filesize in a (shortened) human readable form including the * according units. For instance, 1234567 would be displayed as "1 MB" or * 12345 would be displayed as "12 kB". * The function can display the units in a short or a long form ("1 b" vs. * "1 Byte"). * Optionally, more than one unit part can be displayed. For instance, 1234567 * could also be displayed as "1 MB, 234 kB, 567 b". * * @param int $size The raw filesize as integer * @param bool $verbose Use short or long unit names * @param int $displayed_levels How many unit parts should be displayed * @param String $glue Text used to glue the different unit parts * together * @return String The filesize in human readable form. * @todo Allow "1,3 MB" */ function relsize($size, $verbose = true, $displayed_levels = 1, $glue = ', ', $truncate = false) { $units = [ 'B' => 'Byte', 'kB' => 'Kilobyte', 'MB' => 'Megabyte', 'GB' => 'Gigabyte', 'TB' => 'Terabyte', 'PB' => 'Petabyte', 'EB' => 'Exabyte', 'ZB' => 'Zettabyte', 'YB' => 'Yottabyte', ]; $result = []; foreach ($units as $short => $long) { $remainder = $size % 1024; $template = sprintf('%%.1f %s%%s', $verbose ? $long : $short); $result[$template] = $remainder; $size = floor($size / 1024); if ($size == 0) { break; } } if ($displayed_levels == 1 && count($result) >=2 && !$truncate) { $result = array_slice($result, -2); $fraction = array_shift($result); $template = key($result); $size = array_pop($result); $result = [ $template => $size + $fraction / 1024, ]; } elseif ($displayed_levels > 0) { $result = array_slice($result, -$displayed_levels); } $display = []; foreach ($result as $template => $size) { if ($truncate || $size - floor($size) < 0.1) { $template = str_replace('%.1f', '%u', $template); $size = (int)$size; } $display[] = sprintf($template, $size, ($verbose && $size !== 1) ? 's' : ''); } return implode($glue, array_reverse($display)); } /** * extracts route * * @param string $route route (optional, uses REQUEST_URI otherwise) * * @return string route */ function get_route($route = '') { $route = mb_substr(parse_url($route ?: $_SERVER['REQUEST_URI'], PHP_URL_PATH), mb_strlen($GLOBALS['CANONICAL_RELATIVE_PATH_STUDIP'])); if (mb_strpos($route, 'plugins.php/') !== false) { $trails = explode('plugins.php/', $route); $pieces = explode('/', $trails[1]); $route = 'plugins.php/' . $pieces[0] . (!empty($pieces[1]) ? '/' . $pieces[1] : '') . (!empty($pieces[2]) ? '/' . $pieces[2] : ''); } elseif (mb_strpos($route, 'dispatch.php/') !== false) { $trails = explode('dispatch.php/', $route); $dispatcher = app(\Trails_Dispatcher::class); $pieces = explode('/', $trails[1]); $trail = ''; foreach ($pieces as $index => $piece) { $trail .= ($trail ? '/' : '') . $piece; if ($dispatcher->file_exists($trail . '.php')) { $route = 'dispatch.php/' . $trail . (!empty($pieces[$index+1]) ? '/' . $pieces[$index+1] : ''); } } } while (mb_substr($route, mb_strlen($route)-6, 6) == '/index') { $route = mb_substr($route, 0, mb_strlen($route)-6); } return $route; } /** * compares actual route to requested route * * @param string $requested_route requested route (for help content or tour) * @param string $current_route current route (optional) * * @return boolean result */ function match_route($requested_route, $current_route = '') { if (!$current_route) { $current_route = get_route(); } $route_parts = explode('?', $requested_route); // if base routes don't match, return false without further checks if (!fnmatch($route_parts[0], $current_route)) { return false; } // if no parameters given and base routes do match, return true if (empty($route_parts[1])) { return true; } // extract vars and check if they are set accordingly $vars = []; parse_str($route_parts[1], $vars); if (!count($vars)) { return false; } foreach ($vars as $name => $value) { if (@$_REQUEST[$name] != $value) { return false; } } return true; } function studip_default_exception_handler($exception) { require_once 'lib/visual.inc.php'; // send exception to metrics backend if (class_exists('Metrics')) { $exception_class = mb_strtolower( preg_replace( '/(?<=\w)([A-Z])/', '_\\1', get_class($exception))); Metrics::increment('core.exception.' . $exception_class); } while (ob_get_level()) { ob_end_clean(); } $layout = 'layouts/base.php'; if ($exception instanceof AccessDeniedException) { PageLayout::setTitle(_('Zugriff verweigert')); $status = 403; $template = 'access_denied_exception'; } else if ($exception instanceof CheckObjectException) { $status = 403; $template = 'check_object_exception'; } elseif ($exception instanceof LoginException) { $GLOBALS['auth']->login_if(true); } else { if ($exception instanceOf Trails_Exception) { $status = $exception->getCode(); } else { $status = 500; } error_log($exception->__toString()); $template = 'unhandled_exception'; } header('HTTP/1.1 ' . $status . ' ' . $exception->getMessage()); // ajax requests return JSON instead // re-use the http status code determined above if (!strcasecmp($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '', 'xmlhttprequest')) { header('Content-Type: application/json; charset=UTF-8'); $template = 'json_exception'; $layout = null; } try { if (!isset($GLOBALS['user'])) { $GLOBALS['user'] = new Seminar_User('nobody'); $GLOBALS['perm'] = new Seminar_Perm(); } if (empty($_SESSION['_language'])) { $_SESSION['_language'] = 'de_DE'; } $args = compact('exception', 'status'); ob_start(); echo $GLOBALS['template_factory']->render($template, $args, $layout); } catch (Exception $e) { ob_end_clean(); echo 'Error: ' . htmlReady($e->getMessage()); } exit; } /** * Converts a string to camelCase. * * @param String $string The string that should be converted * @param bool $ucfirst Uppercase the very first character as well * (optional, defaults to false) * @return String containing the converted input string */ function strtocamelcase($string, $ucfirst = false) { $string = mb_strtolower($string); $chunks = preg_split('/\W+/', $string); $chunks = array_map('ucfirst', $chunks); if (!$ucfirst && count($chunks) > 0) { $chunks[0] = mb_strtolower($chunks[0]); } return implode($chunks); } /** * Converts a string to snake_case. * * @param String $string The string that should be converted * @return String containing the converted input string */ function strtosnakecase($string) { $string = preg_replace('/\W+/', '_', $string); $string = preg_replace('/(?<!^)[A-Z]/', '_$0', $string); return mb_strtolower($string); } /** * Converts a string to kebab-case. * * @param String $string The string that should be converted * @return String containing the converted input string */ function strtokebabcase($string) { $string = preg_replace('/\W+/', '-', $string); $string = preg_replace('/(?<!^)[A-Z]/', '-$0', $string); return mb_strtolower($string); } /** * fetch number of rows for a table * for innodb this is not exact, but much faster than count(*) * * @param string $table name of database table * @return int number of rows */ function count_table_rows($table) { $stat = DBManager::get()->fetchOne("SHOW TABLE STATUS LIKE ?", [$table]); return (int)$stat['Rows']; } /** * get the file path relative to the STUDIP_BASE_PATH * * @param string path of the file * @return string relative path of the file */ function studip_relative_path($filepath) { return str_replace($GLOBALS['STUDIP_BASE_PATH'] . DIRECTORY_SEPARATOR, '', $filepath); } /** * converts a given array to a csv format * * @param array $data the data to convert, each row should be an array * @param string $filename full path to a file to write to, if omitted the csv content is returned * @param array $caption assoc array with captions, is written to the first line, $data is filtered by keys * @param string $delimiter sets the field delimiter (one character only) * @param string $enclosure sets the field enclosure (one character only) * @param string $eol sets the end of line format * @return mixed if $filename is given the number of written bytes, else the csv content as string */ function array_to_csv($data, $filename = null, $caption = null, $delimiter = ';' , $enclosure = '"', $eol = "\r\n", $add_bom = true ) { $fp = fopen('php://temp', 'r+'); $fp2 = fopen('php://temp', 'r+'); if ($add_bom) { fwrite($fp2, "\xEF\xBB\xBF"); } if (is_array($caption)) { fputcsv($fp, array_values($caption), $delimiter, $enclosure); rewind($fp); $csv = stream_get_contents($fp); if ($eol != PHP_EOL) { $csv = trim($csv); $csv .= $eol; } fwrite($fp2, $csv); ftruncate($fp, 0); rewind($fp); } foreach ($data as $row) { if (is_array($caption)) { $fields = []; foreach (array_keys($caption) as $fieldname) { $fields[] = $row[$fieldname] ?? ''; } } else { $fields = $row; } fputcsv($fp, $fields, $delimiter, $enclosure); rewind($fp); $csv = stream_get_contents($fp); if ($eol != PHP_EOL) { $csv = trim($csv); $csv .= $eol; } fwrite($fp2, $csv); ftruncate($fp, 0); rewind($fp); } fclose($fp); rewind($fp2); if ($filename === null) { return stream_get_contents($fp2); } else { return file_put_contents($filename, $fp2); } } /** * Delete a file, or a folder and its contents * * @author Aidan Lister <aidan@php.net> * @version 1.0 * @param string $dirname The directory to delete * @return bool Returns true on success, false on failure */ function rmdirr($dirname){ // Simple delete for a file if (is_file($dirname)) { return @unlink($dirname); } else if (!is_dir($dirname)) { return false; } // Loop through the folder $dir = dir($dirname); while (false !== ($entry = $dir->read())) { // Skip pointers if ($entry == '.' || $entry == '..') { continue; } // Deep delete directories if (is_dir("$dirname/$entry") && !is_link("$dirname/$entry")) { rmdirr("$dirname/$entry"); } else { @unlink("$dirname/$entry"); } } // Clean up $dir->close(); return @rmdir($dirname); } /** * Returns the mapping of extensions to supported MIME types. */ function get_mime_types() { static $mime_types = [ // archive types 'gz' => 'application/x-gzip', 'tgz' => 'application/x-gzip', 'bz2' => 'application/x-bzip2', 'zip' => 'application/zip', // document types 'txt' => 'text/plain', 'css' => 'text/css', 'csv' => 'text/csv', 'rtf' => 'application/rtf', 'pdf' => 'application/pdf', 'doc' => 'application/msword', 'xls' => 'application/ms-excel', 'ppt' => 'application/ms-powerpoint', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'odp' => 'application/vnd.oasis.opendocument.presentation', 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', 'odt' => 'application/vnd.oasis.opendocument.text', // image types 'gif' => 'image/gif', 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'jpe' => 'image/jpeg', 'png' => 'image/png', 'bmp' => 'image/x-ms-bmp', 'avif' => 'image/avif', 'avifs' => 'image/avif-sequence', 'heic' => 'image/heic', 'heif' => 'image/heif', 'webp' => 'image/webp', 'apng' => 'image/apng', // audio types 'mp3' => 'audio/mp3', 'oga' => 'audio/ogg', 'wav' => 'audio/wave', // video types 'mpeg' => 'video/mpeg', 'mpg' => 'video/mpeg', 'mpe' => 'video/mpeg', 'qt' => 'video/quicktime', 'mov' => 'video/quicktime', 'avi' => 'video/x-msvideo', 'flv' => 'video/x-flv', 'ogg' => 'application/ogg', 'ogv' => 'video/ogg', 'mp4' => 'video/mp4', 'webm' => 'video/webm', ]; return $mime_types; } /** * Determines an appropriate MIME type for a file based on the * extension of the file name. * * @param string $filename file name to check */ function get_mime_type($filename) { $mime_types = get_mime_types(); $extension = mb_strtolower(pathinfo($filename, PATHINFO_EXTENSION)); if (isset($mime_types[$extension])) { return $mime_types[$extension]; } else { return 'application/octet-stream'; } } function readfile_chunked($filename, $start = null, $end = null) { if (isset($start) && $start < $end) { $chunksize = 1024 * 1024; // how many bytes per chunk $bytes = 0; $handle = fopen($filename, 'rb'); if ($handle === false) { return false; } fseek($handle, $start); while (!feof($handle) && ($p = ftell($handle)) <= $end) { if ($p + $chunksize > $end) { $chunksize = $end - $p + 1; } $buffer = fread($handle, $chunksize); $bytes += strlen($buffer); echo $buffer; } fclose($handle); return $bytes; // return num. bytes delivered like readfile() does. } else { try { $context = get_default_http_stream_context($filename); } catch (InvalidArgumentException $e) { return readfile($filename); } return readfile($filename, false, $context); } } /** * @param string $url * @return resource */ function get_default_http_stream_context($url = '') { $proxy = Config::get()->HTTP_PROXY; if ($url) { $purl = parse_url($url); if (!isset($purl['scheme']) || !in_array($purl['scheme'], ['http', 'https'])) { return stream_context_get_default(); } $host = $purl['host']; $whitelist = array_filter(array_map('trim', explode(',', Config::get()->HTTP_PROXY_IGNORE))); foreach ($whitelist as $whitehost) { if (fnmatch($whitehost, $host)) { $proxy = ''; break; } } } if ($proxy) { $opts = ['http' => ['proxy' => 'tcp://' . $proxy]]; } else { $opts = []; } return stream_context_get_default($opts); } /** * Encodes an uri just like encodeURI() in Javascript would do. * * encodeURI() escapes all characters except: * * A-Z a-z 0-9 ; , / ? : @ & = + $ - _ . ! ~ * ' ( ) # * * @param string $uri * @return string * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI */ function encodeURI(string $uri): string { $replacements = [ '%21' => '!', '%23' => '#', '%24' => '$', '%26' => '&', '%27' => "'", '%28' => '(', '%29' => ')', '%2A' => '*', '%2B' => '+', '%2C' => ',', '%3B' => ';', '%2F' => '/', '%3A' => ':', '%3D' => '=', '%3F' => '?', '%40' => '@', ]; return strtr(rawurlencode($uri), $replacements); } function randomString(int $length = 32): string { $string = ''; while (($len = strlen($string)) < $length) { $size = $length - $len; $bytes = random_bytes($size); $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); } return $string; } /** * escapes special characters for xml use * * @param string $string the string to escape * @return string */ function xml_escape($string) { $string = preg_replace('/[\x00-\x08\x0b\x0c\x0e-\x1f]/', '', $string); return htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); } /** * This function mimics the functionality of the $gettextInterpolate function in JavaScript. * This makes it easier to format text in translatable strings. * * Note that the behavior of this function is simplified in comparison with $gettextInterpolate: * - All placeholders that have a value are replaced with the string value of that value. * Numbers must be pre-formatted before added to the parameters. * - All placeholders that have no replacements in the parameters array are output. * * @param string $gettext_string The translation string to be interpolated. * * @param array $parameters The parameters that replace the placeholders in the translation string. * Array keys are the names of the placeholders while array values are the values that are * placed inside the string. * * @return string The interpolated translation string. */ function studip_interpolate(string $gettext_string, array $parameters) : string { return preg_replace_callback( '/%\{\s*(\w+)\s*\}/', function ($match) use ($parameters): string { if (!isset($parameters[$match[1]])) { throw new Exception('The parameter for the placeholder ' . $match[1] . ' is missing.'); } return $parameters[$match[1]]; }, $gettext_string ); }