diff --git a/404.php b/404.php index da9040d..24a8110 100644 --- a/404.php +++ b/404.php @@ -1,12 +1,11 @@

diff --git a/admin/index.php b/admin/index.php index f9e64a2..876f77a 100644 --- a/admin/index.php +++ b/admin/index.php @@ -2,8 +2,7 @@ session_start(); require("../config.php"); require("../classes/constellation.php"); -require("../header.php"); -require("../footer.php"); +require("../template.php"); if(isset($_COOKIE['user'])&&!isset($_SESSION['user'])) { @@ -66,5 +65,5 @@ else break; } - render_footer(true); + Template::render_footer(true); } \ No newline at end of file diff --git a/admin/login-form.php b/admin/login-form.php index 17af92d..d87afad 100644 --- a/admin/login-form.php +++ b/admin/login-form.php @@ -1,5 +1,5 @@

@@ -29,4 +29,4 @@ render_header(_("Login"));

@@ -69,4 +69,4 @@ render_header(_("Lost password"));
+Template::render_header(_("New user"), true); ?>

Add new user

diff --git a/admin/settings.php b/admin/settings.php index 3178883..500e931 100644 --- a/admin/settings.php +++ b/admin/settings.php @@ -9,7 +9,7 @@ if (isset($_GET['delete'])) Service::delete(); } -render_header(_("Settings"), true); +Template::render_header(_("Settings"), true); ?>

Settings

diff --git a/admin/user.php b/admin/user.php index da2a7c0..9148f1a 100644 --- a/admin/user.php +++ b/admin/user.php @@ -32,7 +32,7 @@ if (isset($_GET['what']) && $_GET['what']=='toggle') $displayed_user->toggle(); } -render_header(_("User"), true); +Template::render_header(_("User"), true); ?>
diff --git a/classes/constellation.php b/classes/constellation.php index 6758690..eafec8c 100644 --- a/classes/constellation.php +++ b/classes/constellation.php @@ -10,6 +10,13 @@ require(__DIR__ . "/token.php"); class Constellation { + /** + * Renders incidents matching specified constraints. + * @param Boolean $future - specifies whether to render old or upcoming incidents + * @param int $offset - specifies offset - used for pagination + * @param int $limit - limits the number of incidents rendered + * @param Boolean $admin - specifies whether to render admin controls + */ public function render_incidents($future=false, $offset=0, $limit = 5, $admin = 0){ global $mysqli; if ($offset<0) @@ -57,6 +64,11 @@ class Constellation } } + /** + * Renders service status - in admin page it returns array so it can be processed further. + * @param boolean $admin + * @return array of services + */ public function render_status($admin = 0){ global $mysqli; diff --git a/classes/incident.php b/classes/incident.php index e30f5f5..7350a52 100644 --- a/classes/incident.php +++ b/classes/incident.php @@ -12,8 +12,13 @@ class Incident private $title; private $username; + /** + * Constructs service from its data. + * @param array $data incident data + */ function __construct($data) { + //TODO: Maybe get data from id? $this->id = $data['status_id']; $this->date = new DateTime("@".$data['time']); $this->date = $this->date->format('Y-m-d H:i:sP'); @@ -27,7 +32,12 @@ class Incident $this->username = $data['username']; } + /** + * Deletes incident by ID. + * @param int ID + */ public static function delete($id){ + //TODO: This should check whether it's admin or their own post... global $mysqli, $message; $stmt = $mysqli->prepare("DELETE FROM services_status WHERE status_id = ?"); @@ -42,6 +52,12 @@ class Incident header("Location: /admin"); } + /** + * Processes submitted form and adds incident unless problem is encountered, + * calling this is possible only for admin or higher rank. Also checks requirements + * for char limits. + * @return void + */ public static function add() { global $mysqli, $message; @@ -120,6 +136,12 @@ class Incident } } + + /** + * Renders incident + * @param Boolean $admin - decides whether admin controls should be rendered + * @return void + */ public function render($admin=0){ global $icons; global $classes, $user; diff --git a/classes/locale-negotiator.php b/classes/locale-negotiator.php new file mode 100644 index 0000000..613d16b --- /dev/null +++ b/classes/locale-negotiator.php @@ -0,0 +1,105 @@ +default_language = $default_language; + //Works only if the server supports the locale + //This basically means $accepted_langs[] = ""; + foreach ($accepted_langs as $key => $value) { + $this->accepted_langs[basename($value)] = self::mb_ucfirst(locale_get_display_language($lang, $lang)); + } + } + + /** + * Returns list of accepted langs so it can be reused for rendering language list for switching... + */ + public function get_accepted_langs(){ + return $this->accepted_langs; + } + + /** + * This methid does ucfirst() on multibyte encodings like UTF-8 - good for edge cases when locale starts with Č or similar. + * @param String $string string + * @return String string with first char uppercase + */ + private static function mb_ucfirst($string) + { + return mb_strtoupper(mb_substr($string, 0, 1)).mb_strtolower(mb_substr($string, 1)); + } + + /** + * This method does the actual negotiation. It has override parameter in case user wants to switch + * languages. + * @param String $override adds language to list of preffered languages with highest priority + * @return String language code that matched best with browser preferences + */ + public function negotiate($override = null){ + $langs = []; + + if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse); + + if (count($lang_parse[1])) { + $langs = array_combine($lang_parse[1], $lang_parse[4]); + + foreach ($langs as $lang => $val) { + //If browser didn't send quality of language, it is 1 by default + if ($val === '') $langs[$lang] = 1; + } + + if (isset($override)) + { + //More important than the best lang of browser + $langs[$override] = 2; + } + + arsort($langs, SORT_NUMERIC); + } + } + + //So we have lang code as value + $langs = array_flip($langs); + //False unless we set it, so we know to set default locale + $best_match = false; + //So we have also lang code as value + $accepted_langs = array_flip($this->accepted_langs); + foreach ($langs as $lang) { + if (strlen($lang)>2){ + if (in_array($lang, $accepted_langs)){ + $best_match = $lang; + break; + } + }else{ + $possible = array_filter($accepted_langs, function($key) { + global $lang; + return strpos($key, $lang) === 0; + }); + + if (count($possible)){ + $best_match = $possible[0]; + break; + } + } + } + + if ($best_match === false){ + $best_match = $this->default_language; + } + + return $best_match; + } +} + diff --git a/classes/service.php b/classes/service.php index 2cad89f..8f11e42 100644 --- a/classes/service.php +++ b/classes/service.php @@ -1,6 +1,6 @@ id = $id; $this->name = $name; $this->status = $status; } + /** + * Returns status of this service + * @return int status + */ public function get_status() { return $this->status; } + /** + * Returns id of this service + * @return int id + */ public function get_id() { return $this->id; } + /** + * Returns name of this service + * @return String name + */ public function get_name() { return $this->name; } + /** + * Processes submitted form and adds service unless problem is encountered, + * calling this is possible only for admin or higher rank. Also checks requirements + * for char limits. + * @return void + */ public static function add() { global $user, $message; @@ -57,6 +82,10 @@ class Service } } + /** + * Deletes this service - first checks if user has permission to do that. + * @return void + */ public static function delete() { global $user; @@ -95,6 +124,11 @@ class Service } } + /** + * Renders current status for services from passed array of services. + * @param Service[] $array array of services + * @return void + */ public static function current_status($array){ global $all, $some, $classes; $statuses = array(0,0,0,0); @@ -118,6 +152,10 @@ class Service echo '
'; } + /** + * Renders this service. + * @return void + */ public function render(){ global $statuses; global $classes; diff --git a/classes/token.php b/classes/token.php index d334223..2b0da73 100644 --- a/classes/token.php +++ b/classes/token.php @@ -4,6 +4,13 @@ */ class Token { + /** + * Generates a new token from user id and randomly generated salt. + * @param int $user ID + * @param String $data associated with token that are important + * @param timestamp $expire expiration time + * @return String token + */ public static function new($id, $data, $expire) { global $mysqli; @@ -16,6 +23,13 @@ class Token return $token; } + /** + * Checks whether token exists in the database and has not expired. + * @param String $token + * @param int $id user ID + * @param String $data + * @return int count of results in database + */ public static function validate_token($token, $id, $data) { global $mysqli; @@ -27,6 +41,11 @@ class Token return $query->fetch_assoc()['count']; } + /** + * Deletes token. + * @param String $token + * @return void + */ public static function delete($token) { global $mysqli; diff --git a/classes/user.php b/classes/user.php index 0193615..3888e53 100644 --- a/classes/user.php +++ b/classes/user.php @@ -1,6 +1,6 @@ rank = $result['permission']; } + /** + * Returns username of this user + * @return String username + */ public function get_username() { return $this->username; } - + + /** + * Returns whether this user is active + * @return Boolean user active status + */ public function is_active() { return $this->active; } - + + /** + * Returns rank of this user + * @return int rank + */ public function get_rank() { return $this->rank; } - + + /** + * Returns full name of this user + * @return String name in "Name Surname" format + */ public function get_name() { return $this->name . " " . $this->surname; } + /** + * Toggles active status of this user. First checks if the user + * making the change has permission to do that. + * @return void + */ public function toggle() { global $mysqli, $message, $user; @@ -78,6 +103,13 @@ class User } } + /** + * Processes submitted form and adds user unless problem is encountered, + * calling this is possible only for Superadmin (other ranks cannot add users) + * or when the installation script is being run. Also checks requirements + * for username and email being unique and char limits. + * @return void + */ public static function add() { global $user, $message, $mysqli; @@ -146,6 +178,13 @@ class User } } + /** + * Processes submitted form and logs user in, unless the user is deactivated or wrong + * password or email has been submitted. The script doesn't let anyone know which + * field was wrong as it is not possible to verify email address from outside admin panel, + * so this actually helps with security :) + * @return void + */ public static function login() { global $message, $mysqli; @@ -200,6 +239,12 @@ class User } } + /** + * Checks whether token is valid (this means is in database and associated + * with the user) and sets session data if it is, so user remains logged in. + * The script deletes the token either way. + * @return void + */ public static function restore_session() { global $mysqli, $message; @@ -225,7 +270,10 @@ class User Token::delete($token); } - + /** + * Renders settings for this user so it can be displayed in admin panel. + * @return void + */ public function render_user_settings() { global $permissions, $user; @@ -307,7 +355,12 @@ class User } - + /** + * Changes user password and deletes all remember tokens so all other sessions + * won't stay logged in without knowing new pass. Uses token when reseting password. + * @param String $token + * @return void + */ public function change_password($token = false) { global $mysqli, $user, $message; @@ -344,6 +397,10 @@ class User $stmt->bind_param("si", $hash, $id); $stmt->execute(); $stmt->close(); + $stmt = $mysqli->prepare("DELETE FROM tokens WHERE user = ? AND data = 'remember'"); + $stmt->bind_param("d", $id); + $stmt->execute(); + $query = $stmt->get_result(); User::logout(); } else{ @@ -366,6 +423,10 @@ class User $stmt->bind_param("si", $hash,$id); $stmt->execute(); $stmt->close(); + $stmt = $mysqli->prepare("DELETE FROM tokens WHERE user = ? AND data = 'remember'"); + $stmt->bind_param("d", $id); + $stmt->execute(); + $query = $stmt->get_result(); } else { @@ -377,6 +438,10 @@ class User } } + /** + * Sends email with link for password reset, link is token protected and valid only once. + * @return void + */ public static function password_link() { global $mysqli; @@ -405,6 +470,10 @@ class User mail($to, $subject, $msg, $headers); } + /** + * Sends email with link for email change confirmation (security reasons), link is token protected and valid only once. + * @return void + */ public function email_link(){ global $mysqli; $email = $_POST['email']; @@ -426,9 +495,12 @@ class User mail($to, $subject, $msg, $headers); } + /** + * Changes email. + * @return void + */ public function change_email() { - //TODO: Get message from this somehow global $mysqli, $message; $time = time(); $token = $_GET['token']; @@ -456,6 +528,10 @@ class User } + /** + * Logs current user out. + * @return void + */ public static function logout(){ global $mysqli; session_unset(); @@ -469,6 +545,10 @@ class User header("Location: /admin"); } + /** + * Changes permissions of current user - only super admin can do this, so it checks permission first. + * @return void + */ public function change_permission(){ global $mysqli, $message, $user; if ($user->get_rank()==0) diff --git a/config.php.template b/config.php.template index 7c5506e..5149d86 100644 --- a/config.php.template +++ b/config.php.template @@ -9,13 +9,27 @@ define("MAILER_ADDRESS", "##mailer_email##"); //Mailer address define("INSTALL_OVERRIDE", false); define("DEFAULT_LANGUAGE", "en_GB"); -require("locale.php"); +require("classes/locale-negotiator.php"); + +if (!isset($_SESSION['locale'])||isset($_GET['lang'])) +{ + $override = ((isset($_GET['lang']))?$_GET['lang']:null); + $negotiator = new LocaleNegotiator(DEFAULT_LANGUAGE); + $best_match = $negotiator->negotiate($override); + $_SESSION['locale'] = $best_match; + setlocale(LC_ALL, $_SESSION['locale'].".UTF-8"); + + bindtextdomain("server-status", __DIR__ . "/locale/"); + bind_textdomain_codeset($_SESSION['locale'], "utf-8"); + textdomain("server-status"); +} + //Database connection $mysqli = new mysqli("##server##","##user##","##password##","##database##"); if ($mysqli->connect_errno) { - printf("Connection failed: %s\n", $mysqli->connect_error); + printf(_("Connection failed: %s\n"), $mysqli->connect_error); exit(); } diff --git a/footer.php b/footer.php deleted file mode 100644 index 501e94e..0000000 --- a/footer.php +++ /dev/null @@ -1,45 +0,0 @@ - -
-
-
-
-
Copyright © Vojtěch Sajdl
-
-
- - - -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - <?php echo $page_name." - ".NAME ?> - - - - - - - - - - -
- - - - - - <?php echo $page_name." - ".NAME ?> - - - - - - - - - - -
-

@@ -47,5 +46,5 @@ render_header("Status");
$value) { - $accepted_langs[$key] = basename($value); -} - -foreach ($accepted_langs as $lang) { - $lang_names[$lang] = mb_ucfirst(locale_get_display_language($lang, $lang)); -} - -if (!isset($_SESSION['locale'])||isset($_GET['lang'])) -{ - if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse); - - if (count($lang_parse[1])) { - $langs = array_combine($lang_parse[1], $lang_parse[4]); - - foreach ($langs as $lang => $val) { - if ($val === '') $langs[$lang] = 1; - } - - if (isset($_GET['lang'])) - { - $langs[$_GET['lang']] = 2; - } - - arsort($langs, SORT_NUMERIC); - } - } - - - - $langs = array_flip($langs); - - $best_match = false; - - foreach ($langs as $lang) { - if (strlen($lang)>2){ - if (in_array($lang, $accepted_langs)){ - $best_match = $lang; - break; - } - }else{ - $possible = array_filter($accepted_langs, function($key) { - global $lang; - return strpos($key, $lang) === 0; - }); - - if (count($possible)){ - $best_match = $possible[0]; - break; - } - } - } - - if ($best_match === false){ - $best_match = DEFAULT_LANGUAGE; - } - - $_SESSION['locale'] = $best_match; -} - - -setlocale(LC_ALL, $_SESSION['locale'].".UTF-8"); - -bindtextdomain("server-status", __DIR__ . "/locale/"); -bind_textdomain_codeset($_SESSION['locale'], "utf-8"); -textdomain("server-status"); \ No newline at end of file diff --git a/template.php b/template.php new file mode 100644 index 0000000..5b0408d --- /dev/null +++ b/template.php @@ -0,0 +1,124 @@ + + + + + + <?php echo $page_name." - ".NAME ?> + + + + + + + + + + +
+ + + + + + <?php echo $page_name." - ".NAME ?> + + + + + + + + + + +
+ +
+
+
+
+
Copyright © Vojtěch Sajdl
+
+
+ + + +
+
+
+
+
+
+ + + + + + + + + + + +