summaryrefslogtreecommitdiffstats
path: root/inc
diff options
context:
space:
mode:
authorSimon Rettberg2022-05-02 18:49:09 +0200
committerSimon Rettberg2022-05-02 18:49:09 +0200
commit5eb8df7432a708284862e4b126e418265d36b4ab (patch)
treed8812cc2bd6245e0b02ca6866a4c14e977e1bb62 /inc
parent[rebootcontrol] Show time of execution for WOL/reboot/shutdown (diff)
downloadslx-admin-5eb8df7432a708284862e4b126e418265d36b4ab.tar.gz
slx-admin-5eb8df7432a708284862e4b126e418265d36b4ab.tar.xz
slx-admin-5eb8df7432a708284862e4b126e418265d36b4ab.zip
[inc/Util] Add types, move error printing functions to their own class
Diffstat (limited to 'inc')
-rw-r--r--inc/arrayutil.inc.php18
-rw-r--r--inc/crypto.inc.php2
-rw-r--r--inc/database.inc.php12
-rw-r--r--inc/download.inc.php6
-rw-r--r--inc/errorhandler.inc.php148
-rw-r--r--inc/module.inc.php2
-rw-r--r--inc/paginate.inc.php12
-rw-r--r--inc/permission.inc.php2
-rw-r--r--inc/render.inc.php6
-rw-r--r--inc/session.inc.php10
-rw-r--r--inc/taskmanager.inc.php2
-rw-r--r--inc/util.inc.php221
12 files changed, 231 insertions, 210 deletions
diff --git a/inc/arrayutil.inc.php b/inc/arrayutil.inc.php
index 3beceb41..d82cdbeb 100644
--- a/inc/arrayutil.inc.php
+++ b/inc/arrayutil.inc.php
@@ -48,4 +48,22 @@ class ArrayUtil
array_multisort($sorter, $sortFlags, $array);
}
+ /**
+ * Check whether $array contains all keys given in $keyList
+ *
+ * @param array $array An array
+ * @param array $keyList A list of strings which must all be valid keys in $array
+ * @return boolean
+ */
+ public static function hasAllKeys(array $array, array $keyList): bool
+ {
+ if (!is_array($array))
+ return false;
+ foreach ($keyList as $key) {
+ if (!isset($array[$key]))
+ return false;
+ }
+ return true;
+ }
+
} \ No newline at end of file
diff --git a/inc/crypto.inc.php b/inc/crypto.inc.php
index d3dd60dc..eb0d344f 100644
--- a/inc/crypto.inc.php
+++ b/inc/crypto.inc.php
@@ -13,7 +13,7 @@ class Crypto
$salt = substr(str_replace('+', '.',
base64_encode(pack('N4', mt_rand(), mt_rand(), mt_rand(), mt_rand()))), 0, 16);
$hash = crypt($password, '$6$' . $salt);
- if (strlen($hash) < 60) Util::traceError('Error hashing password using SHA-512');
+ if (strlen($hash) < 60) ErrorHandler::traceError('Error hashing password using SHA-512');
return $hash;
}
diff --git a/inc/database.inc.php b/inc/database.inc.php
index 8d50a02d..8f2ba6d1 100644
--- a/inc/database.inc.php
+++ b/inc/database.inc.php
@@ -34,7 +34,7 @@ class Database
} catch (PDOException $e) {
if (self::$returnErrors)
return false;
- Util::traceError('Connecting to the local database failed: ' . $e->getMessage());
+ ErrorHandler::traceError('Connecting to the local database failed: ' . $e->getMessage());
}
if (CONFIG_DEBUG) {
Database::exec("SET SESSION sql_mode='STRICT_ALL_TABLES,NO_ENGINE_SUBSTITUTION,ERROR_FOR_DIVISION_BY_ZERO'");
@@ -203,7 +203,7 @@ class Database
self::$lastError = implode("\n", $stmt->errorInfo());
if ($ignoreError === true || ($ignoreError === null && self::$returnErrors))
return false;
- Util::traceError("Database Error: \n" . self::$lastError);
+ ErrorHandler::traceError("Database Error: \n" . self::$lastError);
}
if (CONFIG_DEBUG) {
$duration = microtime(true) - $start;
@@ -221,7 +221,7 @@ class Database
self::$lastError = '(' . $e->getCode() . ') ' . $e->getMessage();
if ($ignoreError === true || ($ignoreError === null && self::$returnErrors))
return false;
- Util::traceError("Database Error: \n" . self::$lastError);
+ ErrorHandler::traceError("Database Error: \n" . self::$lastError);
}
return false;
}
@@ -383,10 +383,10 @@ class Database
{
// Sanity checks
if (array_key_exists($aiKey, $uniqueValues)) {
- Util::traceError("$aiKey must not be in \$uniqueValues");
+ ErrorHandler::traceError("$aiKey must not be in \$uniqueValues");
}
if (is_array($additionalValues) && array_key_exists($aiKey, $additionalValues)) {
- Util::traceError("$aiKey must not be in \$additionalValues");
+ ErrorHandler::traceError("$aiKey must not be in \$additionalValues");
}
// Simple SELECT first
$selectSql = 'SELECT ' . $aiKey . ' FROM ' . $table . ' WHERE 1';
@@ -444,7 +444,7 @@ class Database
// Insert done, retrieve key again
$res = self::queryFirst($selectSql, $uniqueValues);
if ($res === false) {
- Util::traceError('Could not find value in table ' . $table . ' that was just inserted');
+ ErrorHandler::traceError('Could not find value in table ' . $table . ' that was just inserted');
}
return $res[$aiKey];
}
diff --git a/inc/download.inc.php b/inc/download.inc.php
index 39f8e2e2..5a71014e 100644
--- a/inc/download.inc.php
+++ b/inc/download.inc.php
@@ -14,7 +14,7 @@ class Download
if (self::$curlHandle === false) {
self::$curlHandle = curl_init();
if (self::$curlHandle === false) {
- Util::traceError('Could not initialize cURL');
+ ErrorHandler::traceError('Could not initialize cURL');
}
curl_setopt(self::$curlHandle, CURLOPT_CONNECTTIMEOUT, ceil($timeout / 2));
curl_setopt(self::$curlHandle, CURLOPT_TIMEOUT, $timeout);
@@ -30,7 +30,7 @@ class Download
$head = fopen($tmpfile, 'w+b');
unlink($tmpfile);
if ($head === false)
- Util::traceError("Could not open temporary head file $tmpfile for writing.");
+ ErrorHandler::traceError("Could not open temporary head file $tmpfile for writing.");
curl_setopt(self::$curlHandle, CURLOPT_WRITEHEADER, $head);
return self::$curlHandle;
}
@@ -111,7 +111,7 @@ class Download
{
$fh = fopen($target, 'wb');
if ($fh === false)
- Util::traceError("Could not open $target for writing.");
+ ErrorHandler::traceError("Could not open $target for writing.");
$ch = self::initCurl($url, $timeout, $head);
curl_setopt($ch, CURLOPT_FILE, $fh);
$res = curl_exec($ch);
diff --git a/inc/errorhandler.inc.php b/inc/errorhandler.inc.php
new file mode 100644
index 00000000..c7a32b02
--- /dev/null
+++ b/inc/errorhandler.inc.php
@@ -0,0 +1,148 @@
+<?php
+
+class ErrorHandler
+{
+
+
+ /**
+ * Displays an error message and stops script execution.
+ * If CONFIG_DEBUG is true, it will also dump a stack trace
+ * and all globally defined variables.
+ * (As this might reveal sensitive data you should never enable it in production)
+ */
+ public static function traceError($message)
+ {
+ if ((defined('API') && API) || (defined('AJAX') && AJAX) || php_sapi_name() === 'cli') {
+ error_log('API ERROR: ' . $message);
+ error_log(self::formatBacktracePlain(debug_backtrace()));
+ }
+ if (php_sapi_name() === 'cli') {
+ // Don't spam HTML when invoked via cli, above error_log should have gone to stdout/stderr
+ exit(1);
+ }
+ Header('HTTP/1.1 500 Internal Server Error');
+ if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'html') === false) {
+ Header('Content-Type: text/plain; charset=utf-8');
+ echo 'API ERROR: ', $message, "\n", self::formatBacktracePlain(debug_backtrace());
+ exit(0);
+ }
+ Header('Content-Type: text/html; charset=utf-8');
+ echo '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><style>', "\n",
+ ".arg { color: red; background: white; }\n",
+ "h1 a { color: inherit; text-decoration: inherit; font-weight: inherit; }\n",
+ '</style><title>Fatal Error</title></head><body>';
+ echo '<h1>Flagrant <a href="https://www.youtube.com/watch?v=7rrZ-sA4FQc&t=2m2s" target="_blank">S</a>ystem error</h1>';
+ echo "<h2>Message</h2><pre>$message</pre>";
+ if (strpos($message, 'Database') !== false) {
+ echo '<div><a href="install.php">Try running database setup</a></div>';
+ }
+ echo "<br><br>";
+ if (defined('CONFIG_DEBUG') && CONFIG_DEBUG) {
+ global $SLX_ERRORS;
+ if (!empty($SLX_ERRORS)) {
+ echo '<h2>PHP Errors</h2><pre>';
+ foreach ($SLX_ERRORS as $error) {
+ echo htmlspecialchars("{$error['errstr']} ({$error['errfile']}:{$error['errline']}\n");
+ }
+ echo '</pre>';
+ }
+ echo "<h2>Stack Trace</h2>";
+ echo '<pre>', self::formatBacktraceHtml(debug_backtrace()), '</pre>';
+ echo "<h2>Globals</h2><pre>";
+ echo htmlspecialchars(print_r($GLOBALS, true));
+ echo '</pre>';
+ } else {
+ echo <<<SADFACE
+<pre>
+________________________¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶________
+____________________¶¶¶___________________¶¶¶¶_____
+________________¶¶¶_________________________¶¶¶¶___
+______________¶¶______________________________¶¶¶__
+___________¶¶¶_________________________________¶¶¶_
+_________¶¶_____________________________________¶¶¶
+________¶¶_________¶¶¶¶¶___________¶¶¶¶¶_________¶¶
+______¶¶__________¶¶¶¶¶¶__________¶¶¶¶¶¶_________¶¶
+_____¶¶___________¶¶¶¶____________¶¶¶¶___________¶¶
+____¶¶___________________________________________¶¶
+___¶¶___________________________________________¶¶_
+__¶¶____________________¶¶¶¶____________________¶¶_
+_¶¶_______________¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶______________¶¶__
+_¶¶____________¶¶¶¶___________¶¶¶¶¶___________¶¶___
+¶¶¶_________¶¶¶__________________¶¶__________¶¶____
+¶¶_________¶______________________¶¶________¶¶_____
+¶¶¶______¶________________________¶¶_______¶¶______
+¶¶¶_____¶_________________________¶¶_____¶¶________
+_¶¶¶___________________________________¶¶__________
+__¶¶¶________________________________¶¶____________
+___¶¶¶____________________________¶¶_______________
+____¶¶¶¶______________________¶¶¶__________________
+_______¶¶¶¶¶_____________¶¶¶¶¶_____________________
+</pre>
+SADFACE;
+ }
+ echo '</body></html>';
+ exit(0);
+ }
+
+ public static function formatBacktraceHtml($trace): string
+ {
+ $output = '';
+ foreach ($trace as $idx => $line) {
+ $args = array();
+ foreach ($line['args'] as $arg) {
+ $arg = self::formatArgument($arg);
+ $args[] = '<span class="arg">' . htmlspecialchars($arg) . '</span>';
+ }
+ $frame = str_pad('#' . $idx, 3, ' ', STR_PAD_LEFT);
+ $function = htmlspecialchars($line['function']);
+ $args = implode(', ', $args);
+ $file = preg_replace('~(/[^/]+)$~', '<b>$1</b>', htmlspecialchars($line['file']));
+ // Add line
+ $output .= $frame . ' ' . $function . '<b>(</b>'
+ . $args . '<b>)</b>' . ' @ <i>' . $file . '</i>:' . $line['line'] . "\n";
+ }
+ return $output;
+ }
+
+ public static function formatBacktracePlain($trace): string
+ {
+ $output = '';
+ foreach ($trace as $idx => $line) {
+ $args = array();
+ foreach ($line['args'] as $arg) {
+ $args[] = self::formatArgument($arg);
+ }
+ $frame = str_pad('#' . $idx, 3, ' ', STR_PAD_LEFT);
+ $args = implode(', ', $args);
+ // Add line
+ $output .= "\n" . $frame . ' ' . $line['function'] . '('
+ . $args . ')' . ' @ ' . $line['file'] . ':' . $line['line'];
+ }
+ return $output;
+ }
+
+ private static function formatArgument($arg, $expandArray = true)
+ {
+ if (is_string($arg)) {
+ $arg = "'$arg'";
+ } elseif (is_object($arg)) {
+ $arg = 'instanceof ' . get_class($arg);
+ } elseif (is_array($arg)) {
+ if ($expandArray && count($arg) < 20) {
+ $expanded = '';
+ foreach ($arg as $key => $value) {
+ if (!empty($expanded)) {
+ $expanded .= ', ';
+ }
+ $expanded .= $key . ': ' . self::formatArgument($value, false);
+ if (strlen($expanded) > 200)
+ break;
+ }
+ if (strlen($expanded) <= 200)
+ return '[' . $expanded . ']';
+ }
+ $arg = 'Array(' . count($arg) . ')';
+ }
+ return $arg;
+ }
+} \ No newline at end of file
diff --git a/inc/module.inc.php b/inc/module.inc.php
index 55713cd0..ea3af1ba 100644
--- a/inc/module.inc.php
+++ b/inc/module.inc.php
@@ -189,7 +189,7 @@ class Module
{
$modulePath = 'modules/' . $this->name . '/page.inc.php';
if (!file_exists($modulePath)) {
- Util::traceError("Module doesn't have a page: " . $modulePath);
+ ErrorHandler::traceError("Module doesn't have a page: " . $modulePath);
}
require_once $modulePath;
$class = 'Page_' . $this->name;
diff --git a/inc/paginate.inc.php b/inc/paginate.inc.php
index b212e252..3187261c 100644
--- a/inc/paginate.inc.php
+++ b/inc/paginate.inc.php
@@ -19,30 +19,30 @@ class Paginate
$this->currentPage = (isset($_GET['page']) ? (int)$_GET['page'] : 0);
$this->perPage = (int)$perPage;
if ($this->currentPage < 0) {
- Util::traceError('Current page < 0');
+ ErrorHandler::traceError('Current page < 0');
}
if ($this->perPage < 1) {
- Util::traceError('Per page < 1');
+ ErrorHandler::traceError('Per page < 1');
}
// Query
if (!preg_match('/\s*SELECT\s/is', $query)) {
- Util::traceError('Query has to start with SELECT!');
+ ErrorHandler::traceError('Query has to start with SELECT!');
}
// XXX: MySQL only
if (preg_match('/^mysql/i', CONFIG_SQL_DSN)) {
// Sanity: Check for LIMIT specification at the end
if (preg_match('/LIMIT\s+(\d+|\:\w+|\?)\s*,\s*(\d+|\:\w+|\?)(\s|;)*(\-\-.*)?$/is', $query)) {
- Util::traceError("You cannot pass a query containing a LIMIT to the Paginator class!");
+ ErrorHandler::traceError("You cannot pass a query containing a LIMIT to the Paginator class!");
}
// Sanity: no comment or semi-colon at end (sloppy, might lead to false negatives)
if (preg_match('/(\-\-|;)(\s|[^\'"`])*$/is', $query)) {
- Util::traceError("Your query must not end in a comment or semi-colon!");
+ ErrorHandler::traceError("Your query must not end in a comment or semi-colon!");
}
// Don't use SQL_CALC_FOUND_ROWS as it leads to filesort frequently thus being slower than two queries
// See https://www.percona.com/blog/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/
} else {
- Util::traceError('Unsupported database engine');
+ ErrorHandler::traceError('Unsupported database engine');
}
// Mangle URL
if ($url === false) $url = $_SERVER['REQUEST_URI'];
diff --git a/inc/permission.inc.php b/inc/permission.inc.php
index cd9cc43c..abeabb1d 100644
--- a/inc/permission.inc.php
+++ b/inc/permission.inc.php
@@ -11,7 +11,7 @@ class Permission
public static function get($permission)
{
- if (!isset(self::$permissions[$permission])) Util::traceError('Invalid permission: ' . $permission);
+ if (!isset(self::$permissions[$permission])) ErrorHandler::traceError('Invalid permission: ' . $permission);
return self::$permissions[$permission];
}
diff --git a/inc/render.inc.php b/inc/render.inc.php
index 2c3a1da7..697646c5 100644
--- a/inc/render.inc.php
+++ b/inc/render.inc.php
@@ -28,7 +28,7 @@ class Render
public static function init()
{
if (self::$mustache !== false)
- Util::traceError('Called Render::init() twice!');
+ ErrorHandler::traceError('Called Render::init() twice!');
$options = array();
$tmp = '/tmp/bwlp-cache';
$dir = is_dir($tmp);
@@ -262,10 +262,10 @@ class Render
public static function closeTag($tag)
{
if (empty(self::$tags))
- Util::traceError('Tried to close tag ' . $tag . ' when no open tags exist.');
+ ErrorHandler::traceError('Tried to close tag ' . $tag . ' when no open tags exist.');
$last = array_pop(self::$tags);
if ($last !== $tag)
- Util::traceError('Tried to close tag ' . $tag . ' when last opened tag was ' . $last);
+ ErrorHandler::traceError('Tried to close tag ' . $tag . ' when last opened tag was ' . $last);
self::$body .= '</' . $tag . '>';
}
diff --git a/inc/session.inc.php b/inc/session.inc.php
index fc875669..3afbc4ce 100644
--- a/inc/session.inc.php
+++ b/inc/session.inc.php
@@ -13,7 +13,7 @@ class Session
private static function generateSessionId(string $salt)
{
if (self::$sid !== false)
- Util::traceError('Error: Asked to generate session id when already set.');
+ ErrorHandler::traceError('Error: Asked to generate session id when already set.');
self::$sid = sha1($salt . ','
. mt_rand(0, 65535)
. $_SERVER['REMOTE_ADDR']
@@ -74,7 +74,7 @@ class Session
public static function set(string $key, $value, $validMinutes = 60)
{
if (self::$data === false)
- Util::traceError('Tried to set session data with no active session');
+ ErrorHandler::traceError('Tried to set session data with no active session');
if ($value === false) {
unset(self::$data[$key]);
} else {
@@ -86,7 +86,7 @@ class Session
private static function loadSessionId(): bool
{
if (self::$sid !== false)
- Util::traceError('Error: Asked to load session id when already set.');
+ ErrorHandler::traceError('Error: Asked to load session id when already set.');
if (empty($_COOKIE['sid']))
return false;
$id = preg_replace('/[^a-zA-Z0-9]/', '', $_COOKIE['sid']);
@@ -115,7 +115,7 @@ class Session
private static function readSessionData(): bool
{
if (self::$data !== false)
- Util::traceError('Tried to call read session data twice');
+ ErrorHandler::traceError('Tried to call read session data twice');
$row = Database::queryFirst("SELECT userid, dateline, lastip, fixedip, data FROM session WHERE sid = :sid",
['sid' => self::$sid]);
$now = time();
@@ -149,7 +149,7 @@ class Session
$ret = setcookie('sid', self::$sid, time() + CONFIG_SESSION_TIMEOUT,
null, null, !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off', true);
if (!$ret)
- Util::traceError('Error: Could not set Cookie for Client (headers already sent)');
+ ErrorHandler::traceError('Error: Could not set Cookie for Client (headers already sent)');
}
register_shutdown_function(function () {
Session::saveInternal();
diff --git a/inc/taskmanager.inc.php b/inc/taskmanager.inc.php
index f7c72e04..a21dc5f7 100644
--- a/inc/taskmanager.inc.php
+++ b/inc/taskmanager.inc.php
@@ -288,7 +288,7 @@ class Taskmanager
if (count($parts) !== 2) {
error_log('TM: Invalid reply, no "," in payload');
} elseif ($parts[0] === 'ERROR') {
- Util::traceError('Taskmanager remote error: ' . $parts[1]);
+ ErrorHandler::traceError('Taskmanager remote error: ' . $parts[1]);
} elseif ($parts[0] === 'WARNING') {
Message::addWarning('main.taskmanager-warning', $parts[1]);
} else {
diff --git a/inc/util.inc.php b/inc/util.inc.php
index c3e70f89..d95265f4 100644
--- a/inc/util.inc.php
+++ b/inc/util.inc.php
@@ -5,158 +5,17 @@ class Util
private static $redirectParams = array();
/**
- * Displays an error message and stops script execution.
- * If CONFIG_DEBUG is true, it will also dump a stack trace
- * and all globally defined variables.
- * (As this might reveal sensitive data you should never enable it in production)
- */
- public static function traceError($message)
- {
- if ((defined('API') && API) || (defined('AJAX') && AJAX) || php_sapi_name() === 'cli') {
- error_log('API ERROR: ' . $message);
- error_log(self::formatBacktracePlain(debug_backtrace()));
- }
- if (php_sapi_name() === 'cli') {
- // Don't spam HTML when invoked via cli, above error_log should have gone to stdout/stderr
- exit(1);
- }
- Header('HTTP/1.1 500 Internal Server Error');
- if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'html') === false ) {
- Header('Content-Type: text/plain; charset=utf-8');
- echo 'API ERROR: ', $message, "\n", self::formatBacktracePlain(debug_backtrace());
- exit(0);
- }
- Header('Content-Type: text/html; charset=utf-8');
- echo '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><style>', "\n",
- ".arg { color: red; background: white; }\n",
- "h1 a { color: inherit; text-decoration: inherit; font-weight: inherit; }\n",
- '</style><title>Fatal Error</title></head><body>';
- echo '<h1>Flagrant <a href="https://www.youtube.com/watch?v=7rrZ-sA4FQc&t=2m2s" target="_blank">S</a>ystem error</h1>';
- echo "<h2>Message</h2><pre>$message</pre>";
- if (strpos($message, 'Database') !== false) {
- echo '<div><a href="install.php">Try running database setup</a></div>';
- }
- echo "<br><br>";
- if (defined('CONFIG_DEBUG') && CONFIG_DEBUG) {
- global $SLX_ERRORS;
- if (!empty($SLX_ERRORS)) {
- echo '<h2>PHP Errors</h2><pre>';
- foreach ($SLX_ERRORS as $error) {
- echo htmlspecialchars("{$error['errstr']} ({$error['errfile']}:{$error['errline']}\n");
- }
- echo '</pre>';
- }
- echo "<h2>Stack Trace</h2>";
- echo '<pre>', self::formatBacktraceHtml(debug_backtrace()), '</pre>';
- echo "<h2>Globals</h2><pre>";
- echo htmlspecialchars(print_r($GLOBALS, true));
- echo '</pre>';
- } else {
- echo <<<SADFACE
-<pre>
-________________________¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶________
-____________________¶¶¶___________________¶¶¶¶_____
-________________¶¶¶_________________________¶¶¶¶___
-______________¶¶______________________________¶¶¶__
-___________¶¶¶_________________________________¶¶¶_
-_________¶¶_____________________________________¶¶¶
-________¶¶_________¶¶¶¶¶___________¶¶¶¶¶_________¶¶
-______¶¶__________¶¶¶¶¶¶__________¶¶¶¶¶¶_________¶¶
-_____¶¶___________¶¶¶¶____________¶¶¶¶___________¶¶
-____¶¶___________________________________________¶¶
-___¶¶___________________________________________¶¶_
-__¶¶____________________¶¶¶¶____________________¶¶_
-_¶¶_______________¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶______________¶¶__
-_¶¶____________¶¶¶¶___________¶¶¶¶¶___________¶¶___
-¶¶¶_________¶¶¶__________________¶¶__________¶¶____
-¶¶_________¶______________________¶¶________¶¶_____
-¶¶¶______¶________________________¶¶_______¶¶______
-¶¶¶_____¶_________________________¶¶_____¶¶________
-_¶¶¶___________________________________¶¶__________
-__¶¶¶________________________________¶¶____________
-___¶¶¶____________________________¶¶_______________
-____¶¶¶¶______________________¶¶¶__________________
-_______¶¶¶¶¶_____________¶¶¶¶¶_____________________
-</pre>
-SADFACE;
- }
- echo '</body></html>';
- exit(0);
- }
-
- private static function formatArgument($arg, $expandArray = true)
- {
- if (is_string($arg)) {
- $arg = "'$arg'";
- } elseif (is_object($arg)) {
- $arg = 'instanceof ' . get_class($arg);
- } elseif (is_array($arg)) {
- if ($expandArray && count($arg) < 20) {
- $expanded = '';
- foreach ($arg as $key => $value) {
- if (!empty($expanded)) {
- $expanded .= ', ';
- }
- $expanded .= $key . ': ' . self::formatArgument($value, false);
- if (strlen($expanded) > 200)
- break;
- }
- if (strlen($expanded) <= 200)
- return '[' . $expanded . ']';
- }
- $arg = 'Array(' . count($arg) . ')';
- }
- return $arg;
- }
-
- public static function formatBacktraceHtml($trace)
- {
- $output = '';
- foreach ($trace as $idx => $line) {
- $args = array();
- foreach ($line['args'] as $arg) {
- $arg = self::formatArgument($arg);
- $args[] = '<span class="arg">' . htmlspecialchars($arg) . '</span>';
- }
- $frame = str_pad('#' . $idx, 3, ' ', STR_PAD_LEFT);
- $function = htmlspecialchars($line['function']);
- $args = implode(', ', $args);
- $file = preg_replace('~(/[^/]+)$~', '<b>$1</b>', htmlspecialchars($line['file']));
- // Add line
- $output .= $frame . ' ' . $function . '<b>(</b>'
- . $args . '<b>)</b>' . ' @ <i>' . $file . '</i>:' . $line['line'] . "\n";
- }
- return $output;
- }
-
- public static function formatBacktracePlain($trace)
- {
- $output = '';
- foreach ($trace as $idx => $line) {
- $args = array();
- foreach ($line['args'] as $arg) {
- $args[] = self::formatArgument($arg);
- }
- $frame = str_pad('#' . $idx, 3, ' ', STR_PAD_LEFT);
- $args = implode(', ', $args);
- // Add line
- $output .= "\n" . $frame . ' ' . $line['function'] . '('
- . $args . ')' . ' @ ' . $line['file'] . ':' . $line['line'];
- }
- return $output;
- }
-
- /**
* Redirects the user via a '302 Moved' header.
* An active session will be saved, any messages that haven't
* been displayed yet will be appended to the redirect.
+ *
* @param string|false $location Location to redirect to. "false" to redirect to same URL (useful after POSTs)
* @param bool $preferRedirectPost if true, use the value from $_POST['redirect'] instead of $location
*/
- public static function redirect($location = false, $preferRedirectPost = false)
+ public static function redirect($location = false, bool $preferRedirectPost = false)
{
if ($location === false) {
- $location = preg_replace('/([&?])message\[\]\=[^&]*/', '\1', $_SERVER['REQUEST_URI']);
+ $location = preg_replace('/([&?])message\[\]=[^&]*/', '\1', $_SERVER['REQUEST_URI']);
}
$messages = Message::toRequest();
if ($preferRedirectPost
@@ -189,7 +48,7 @@ SADFACE;
exit(0);
}
- public static function addRedirectParam($key, $value)
+ public static function addRedirectParam(string $key, string $value)
{
self::$redirectParams[] = $key . '=' . urlencode($value);
}
@@ -201,7 +60,7 @@ SADFACE;
* token, this function will return false and display an error.
* If the token matches, or the user is not logged in, it will return true.
*/
- public static function verifyToken()
+ public static function verifyToken(): bool
{
if (!User::isLoggedIn() && Session::get('token') === false)
return true;
@@ -218,12 +77,12 @@ SADFACE;
* _word_ is underlined
* \n is line break
*/
- public static function markup($string)
+ public static function markup(string $string): string
{
$string = htmlspecialchars($string);
- $string = preg_replace('#(^|[\n \-_/\.])\*(.+?)\*($|[ \-_/\.\!\?,:])#is', '$1<b>$2</b>$3', $string);
- $string = preg_replace('#(^|[\n \-\*/\.])_(.+?)_($|[ \-\*/\.\!\?,:])#is', '$1<u>$2</u>$3', $string);
- $string = preg_replace('#(^|[\n \-_\*\.])/(.+?)/($|[ \-_\*\.\!\?,:])#is', '$1<i>$2</i>$3', $string);
+ $string = preg_replace('#(^|[\n \-_/.])\*(.+?)\*($|[ \-_/.!?,:])#is', '$1<b>$2</b>$3', $string);
+ $string = preg_replace('#(^|[\n \-*/.])_(.+?)_($|[ \-*/.!?,:])#is', '$1<u>$2</u>$3', $string);
+ $string = preg_replace('#(^|[\n \-_*.])/(.+?)/($|[ \-_*.!?,:])#is', '$1<i>$2</i>$3', $string);
return nl2br($string);
}
@@ -236,7 +95,7 @@ SADFACE;
* @param int $shift how many units to skip, i.e. if you pass in KiB or MiB
* @return string human readable string representing the given file size
*/
- public static function readableFileSize($bytes, $decimals = -1, $shift = 0)
+ public static function readableFileSize(float $bytes, int $decimals = -1, int $shift = 0): string
{
// round doesn't reliably work for large floats, pick workaround depending on OS
if (PHP_INT_SIZE === 4) {
@@ -257,17 +116,26 @@ SADFACE;
return sprintf("%.{$decimals}f", $bytes) . "\xe2\x80\x89" . $sz[$factor + $shift];
}
- public static function sanitizeFilename($name)
+ public static function sanitizeFilename(string $name)
{
return preg_replace('/[^a-zA-Z0-9_\-]+/', '_', $name);
}
- public static function safePath($path, $prefix = '')
+ /**
+ * Make sure given path is not absolute, and does not contain '..', or weird characters.
+ * Returns sanitized path, or false if invalid. If prefix is given, also make sure
+ * $path starts with it.
+ *
+ * @param string $path path to check for safety
+ * @param string $prefix required prefix of $path
+ * @return false|string
+ */
+ public static function safePath(string $path, string $prefix = '')
{
if (empty($path))
return false;
$path = trim($path);
- if ($path[0] == '/' || preg_match('/[\x00-\x19\?\*]/', $path))
+ if ($path[0] == '/' || preg_match('/[\x00-\x19?*]/', $path))
return false;
if (strpos($path, '..') !== false)
return false;
@@ -288,7 +156,7 @@ SADFACE;
* @param int $code the code to turn into an error description
* @return string the error description
*/
- public static function uploadErrorString($code)
+ public static function uploadErrorString(int $code): string
{
switch ($code) {
case UPLOAD_ERR_INI_SIZE:
@@ -326,7 +194,7 @@ SADFACE;
* @param string $ip_addr input to check
* @return boolean true iff $ip_addr is a valid public ipv4 address
*/
- public static function isPublicIpv4($ip_addr)
+ public static function isPublicIpv4(string $ip_addr): bool
{
if (!preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $ip_addr))
return false;
@@ -348,23 +216,6 @@ SADFACE;
}
/**
- * Check whether $arrax contains all keys given in $keyList
- * @param array $array An array
- * @param array $keyList A list of strings which must all be valid keys in $array
- * @return boolean
- */
- public static function hasAllKeys($array, $keyList)
- {
- if (!is_array($array))
- return false;
- foreach ($keyList as $key) {
- if (!isset($array[$key]))
- return false;
- }
- return true;
- }
-
- /**
* Send a file to user for download.
*
* @param string $file path of local file
@@ -374,7 +225,7 @@ SADFACE;
* true: error while reading the file
* - on success, the function does not return
*/
- public static function sendFile($file, $name, $delete = false)
+ public static function sendFile(string $file, string $name, bool $delete = false): bool
{
while ((@ob_get_level()) > 0)
@ob_end_clean();
@@ -404,7 +255,7 @@ SADFACE;
* @param bool $secure true = only use strong random sources
* @return string|bool string of requested length, false on error
*/
- public static function randomBytes($length, $secure = true)
+ public static function randomBytes(int $length, bool $secure = true)
{
if (function_exists('random_bytes')) {
try {
@@ -453,7 +304,7 @@ SADFACE;
/**
* @return string a random UUID, v4.
*/
- public static function randomUuid()
+ public static function randomUuid(): string
{
$b = unpack('h8a/h4b/h12c', self::randomBytes(12));
return sprintf('%08s-%04s-%04x-%04x-%012s',
@@ -481,10 +332,11 @@ SADFACE;
/**
* Transform timestamp to easily readable string.
* The format depends on how far the timestamp lies in the past.
+ *
* @param int $ts unix timestamp
* @return string human readable representation
*/
- public static function prettyTime($ts)
+ public static function prettyTime(int $ts): string
{
settype($ts, 'int');
if ($ts === 0)
@@ -507,10 +359,11 @@ SADFACE;
/**
* Return localized strings for yes or no depending on $bool
+ *
* @param bool $bool Input to evaluate
* @return string Yes or No, in user's selected language
*/
- public static function boolToString($bool)
+ public static function boolToString(bool $bool): string
{
if ($bool)
return Dictionary::translate('lang_yes', true);
@@ -519,11 +372,12 @@ SADFACE;
/**
* Format a duration, in seconds, into a readable string.
+ *
* @param int $seconds The number to format
- * @param int $showSecs whether to show seconds, or rather cut after minutes
+ * @param bool $showSecs whether to show seconds, or rather cut after minutes
* @return string
*/
- public static function formatDuration($seconds, $showSecs = true)
+ public static function formatDuration(int $seconds, bool $showSecs = true): string
{
settype($seconds, 'int');
static $UNITS = ['y' => 31536000, 'mon' => 2592000, 'd' => 86400];
@@ -550,9 +404,10 @@ SADFACE;
* was a weird problem where firefox would keep sending a cookie with
* path /slx-admin/ but trying to delete it from /slx-admin, which php's
* setcookie automatically sends by default, did not clear it.
+ *
* @param string $name cookie name
*/
- public static function clearCookie($name)
+ public static function clearCookie(string $name)
{
$parts = explode('/', $_SERVER['SCRIPT_NAME']);
$path = '';
@@ -567,7 +422,7 @@ SADFACE;
/**
* Remove any non-utf8 sequences from string.
*/
- public static function cleanUtf8(string $string) : string
+ public static function cleanUtf8(string $string): string
{
// https://stackoverflow.com/a/1401716/2043481
$regex = '/
@@ -586,7 +441,7 @@ SADFACE;
/**
* Remove non-printable < 0x20 chars from ANSI string, then convert to UTF-8
*/
- public static function ansiToUtf8(string $string) : string
+ public static function ansiToUtf8(string $string): string
{
$regex = '/
(