Forked from
Stud.IP / Stud.IP
3358 commits behind the upstream repository.
-
Closes #1192 Merge request studip/studip!706
Closes #1192 Merge request studip/studip!706
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
functions.php 59.91 KiB
<?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;
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 ($SEM_TYPE[$row['status']]['name'] == $SEM_TYPE_MISC_NAME) {
$type = _('Veranstaltung');
} else {
$type = $SEM_TYPE[$row['status']]['name'];
}
if (!$type) {
$type = _('Veranstaltung');
}
$name = $row['Name'];
} 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);
$type = $INST_TYPE[$row['type']]['name'];
if (!$type) {
$type = _('Einrichtung');
}
$name = $row['Name'];
}
return compact('name', 'type');
}
/**
* Returns a sorm object for a given range_id
*
* @param string the range_id
* @return 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++;
$group=($index % 7) + 1;
return $group;
}
/**
* 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;
global $user, $_fullname_sql;
if (!$user_id) {
$user_id = $user->id;
}
if (User::findCurrent()->id === $user_id) {
$fullname = User::findCurrent()->getFullName($format);
return $htmlready ? htmlReady($fullname) : $fullname;
}
$hash = md5($user_id . $format);
if (!isset($cache[$hash])) {
$query = "SELECT {$_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 = null;
$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;
}
/**
* gets an entry from the studip configuration table
*
* @param string $key the key for the config entry
* @return string the value
* @deprecated since Stud.IP 5.0
*/
function get_config($key)
{
return Config::get()->$key;
}
/**
* reset the order-positions for the lecturers in the passed seminar,
* starting at the passed position
*
* @param string $s_id the seminar to work on
* @param int $position the position to start with
*
* @return void
*/
function re_sort_dozenten($s_id, $position)
{
$query = "UPDATE seminar_user
SET position = position - 1
WHERE Seminar_id = ? AND status = 'dozent' AND position > ?";
$statement = DBManager::get()->prepare($query);
$statement->execute([$s_id, $position]);
}
/**
* reset the order-positions for the tutors in the passed seminar,
* starting at the passed position
*
* @param string $s_id the seminar to work on
* @param int $position the position to start with
*
* @return void
*/
function re_sort_tutoren($s_id, $position)
{
$query = "UPDATE seminar_user
SET position = position - 1
WHERE Seminar_id = ? AND status = 'tutor' AND position > ?";
$statement = DBManager::get()->prepare($query);
$statement->execute([$s_id, $position]);
}
/**
* return the highest position-number increased by one for the
* passed user-group in the passed seminar
*
* @param string $status can be on of 'tutor', 'dozent', ...
* @param string $seminar_id the seminar to work on
*
* @return int the next available position
*/
function get_next_position($status, $seminar_id)
{
$query = "SELECT MAX(position) + 1
FROM seminar_user
WHERE Seminar_id = ? AND status = ?";
$statement = DBManager::get()->prepare($query);
$statement->execute([$seminar_id, $status]);
return $statement->fetchColumn() ?: 0;
}
/**
* 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;
global $perm, $user;
$u_id = $user->id;
// root darf sowieso ueberall dran
if ($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 ($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 ($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 $perm, $user, $_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 && $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 && $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([$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([$user->id, $search_str]);
while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
$search_result[$row['Institut_id']] = [
'type' => 'inst',
'name' => $row['Name'],
];
}
if ($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([$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([$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([$user->id, $search_str]);
while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
$search_result[$row['Institut_id']] = [
'type' => 'inst',
'name' => $row['Name'],
];
}
}
} elseif ($perm->have_perm('tutor') || $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([$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([$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([$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([
$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];
$help_query = sprintf('https://hilfe.studip.de/help/%s/%s/%s',
$version, $lang, $keyword);
return $help_query;
}
/**
* 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'
))
);
}
}
/**
* Special stud.ip version of json_decode() that also converts the data
* from utf8 and creates an associative array by default (this differs
* from the default behavior of json_decode() !).
*
* @param String $json
* @param bool $assoc
* @param int $depth
* @param int $options
* @deprecated since Stud.IP 5.0
*/
function studip_json_decode($json, $assoc = true, $depth = 512, $options = 0)
{
$data = json_decode($json, $assoc, $depth, $options);
return $data;
}
/**
* Special stud.ip version of json_decode() that also converts the data
* to utf8.
*
* @param mixed $data
* @param int $options
* @param int $depth
* @deprecated since Stud.IP 5.0
*/
function studip_json_encode($data, $options = 0)
{
$json = json_encode($data, $options);
return $json;
}
/**
* 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';
$title = $SEM_CLASS[$SEM_TYPE[$sem_type]['class']][$class_index] ??
$DEFAULT_TITLE_FOR_STATUS[$type][$index] ?? _('unbekannt');
return $title;
}
/**
* 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] . ($pieces[1] ? '/' . $pieces[1] : '') . ($pieces[2] ? '/' . $pieces[2] : '');
} elseif (mb_strpos($route, 'dispatch.php/') !== false) {
$trails = explode('dispatch.php/', $route);
$dispatcher = new StudipDispatcher();
$pieces = explode('/', $trails[1]);
$trail = '';
foreach ($pieces as $index => $piece) {
$trail .= ($trail ? '/' : '') . $piece;
if ($dispatcher->file_exists($trail . '.php')) {
$route = 'dispatch.php/' . $trail . (isset($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 {
$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);
$string = mb_strtolower($string);
return $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);
$string = mb_strtolower($string);
return $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);
}
/**
* 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)
{
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',
];
$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);
}