diff --git a/classes/mailer.php b/classes/mailer.php
new file mode 100644
index 0000000..d8f4eb7
--- /dev/null
+++ b/classes/mailer.php
@@ -0,0 +1,175 @@
+is_utf8($to) ) {
+ $elements = explode('@', $to);
+ $domainpart = EncodePunycodeIDN(array_pop($elements)); // Convert domain part to ascii
+ $to = $elements[0] . '@' . $domainpart; // Reassemble tge full email address
+ syslog(1,"email: " .$to);
+ }
+
+ // Send using PHP mailer if it is enabled
+ if ( PHP_MAILER ) {
+ require_once(PHP_MAILER_PATH .'/Exception.php'); /* Exception class. */
+ require_once(PHP_MAILER_PATH .'/PHPMailer.php'); /* The main PHPMailer class. */
+
+ if ( PHP_MAILER_SMTP ) {
+ require_once(PHP_MAILER_PATH .'/SMTP.php'); /* SMTP class, needed if you want to use SMTP. */
+ }
+
+ $phpmail = new PHPMailer(false);
+
+ $phpmail->setFrom(MAILER_ADDRESS, MAILER_NAME);
+ $phpmail->addReplyTo(MAILER_ADDRESS, MAILER_NAME);
+ //$phpmail->Debugoutput = error_log;
+
+ // Define SMTP parameters if enabled
+ if ( PHP_MAILER_SMTP ) {
+
+ $phpmail->isSMTP();
+ $phpmail->Host = PHP_MAILER_HOST;
+ $phpmail->Port = PHP_MAILER_PORT;
+ $phpmail->SMTPSecure = PHP_MAILER_SECURE;
+ //$phpmail->SMTPDebug = 2; // Enable for debugging
+
+ // Handle authentication for SMTP if enabled
+ if ( !empty(PHP_MAILER_USER) ) {
+ $phpmail->SMTPAuth = true;
+ $phpmail->Username = PHP_MAILER_USER;
+ $phpmail->Password = PHP_MAILER_PASS;
+ }
+ }
+
+ $phpmail->addAddress($to);
+ $phpmail->Subject = $subject;
+ // Send HMTL mail
+ if ( $html ) {
+ $phpmail->msgHtml($message);
+ $phpmail->AltBody = $this->convert_html_to_plain_txt($message, false);
+ } else {
+ $phpmail->Body = $message; // Send plain text
+ }
+
+ $phpmail->isHtml($html); // use htmlmail if enabled
+ if ( ! $phpmail->send() ) {
+ // TODO Log error message $phpmail->ErrorInfo;
+ return false;
+ }
+ return true;
+
+ } else {
+ // Use standard PHP mail() function
+ $headers = "Content-Type: $content_type; \"charset=utf-8\" ".PHP_EOL;
+ $headers .= "MIME-Version: 1.0 ".PHP_EOL;
+ $headers .= "From: ".MAILER_NAME.' <'.MAILER_ADDRESS.'>'.PHP_EOL;
+ $headers .= "Reply-To: ".MAILER_NAME.' <'.MAILER_ADDRESS.'>'.PHP_EOL;
+
+ mail($to, $subject, $message, $headers);
+ // TODO log error message if mail fails
+ return true;
+ }
+
+ }
+ /**
+ * Tries to verify the domain using dns request against an MX record of the domain part
+ * of the passed email address. The code also handles IDN/Punycode formatted addresses which
+ * contains utf8 characters.
+ * Original code from https://stackoverflow.com/questions/19261987/how-to-check-if-an-email-address-is-real-or-valid-using-php/19262381
+ * @param String $email Email address to check
+ * @return boolean True if MX record exits, false if otherwise
+ */
+ public function verify_domain($email){
+ // TODO - Handle idn/punycode domain names without being dependent on PHP native libs.
+ $domain = explode('@', $email);
+ $domain = EncodePunycodeIDN(array_pop($domain).'.'); // Add dot at end of domain to avoid local domain lookups
+ syslog(1,$domain);
+ return checkdnsrr($domain, 'MX');
+ }
+
+
+ /**
+ * Check if string contains non-english characters (detect IDN/Punycode enabled domains)
+ * Original code from: https://stackoverflow.com/questions/13120475/detect-non-english-chars-in-a-string
+ * @param String $str String to check for extended characters
+ * @return boolean True if extended characters, false otherwise
+ */
+ public function is_utf8($str)
+ {
+ if (strlen($str) == strlen(utf8_decode($str))) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Takes the input from an HTML email and convert it to plain text
+ * This is commonly used when sending HTML emails as a backup for email clients who can only view, or who choose to only view,
+ * Original code from https://github.com/DukeOfMarshall/PHP---JSON-Email-Verification/blob/master/EmailVerify.class.php
+ * plain text emails
+ * @param string $content The body part of the email to convert to plain text.
+ * @param boolean $remove_links Set to true if links should be removed from email
+ * @return String pain text version
+ */
+ public function convert_html_to_plain_txt($content, $remove_links=false){
+ // TODO does not handle unsubscribe/manage subscription text very well.
+ // Replace HTML line breaks with text line breaks
+ $plain_text = str_ireplace(array(" "," "), "\n\r", $content);
+
+ // Remove the content between the tags that wouldn't normally get removed with the strip_tags function
+ $plain_text = preg_replace(array('@
]*?>.*?@siu',
+ '@@siu',
+ '@@siu',
+ '@@siu',
+ ), "", $plain_text); // Remove everything from between the tags that doesn't get removed with strip_tags function
+
+ // If the user has chosen to preserve the addresses from links
+ if(!$remove_links){
+ $plain_text = strip_tags(preg_replace('//', ' $1 ', $plain_text));
+ }
+
+ // Remove HTML spaces
+ $plain_text = str_replace(" ", "", $plain_text);
+
+ // Replace multiple line breaks with a single line break
+ $plain_text = preg_replace("/(\s){3,}/","\r\n\r\n",trim($plain_text));
+
+ return $plain_text;
+ }
+
+}
\ No newline at end of file
diff --git a/classes/notification.php b/classes/notification.php
new file mode 100644
index 0000000..51b3a3c
--- /dev/null
+++ b/classes/notification.php
@@ -0,0 +1,139 @@
+prepare("SELECT services.id, services.name FROM services INNER JOIN services_status on services.id = services_status.service_id WHERE services_status.status_id = ?");
+ $stmt->bind_param("i", $status_id);
+ $stmt->execute();
+ $query = $stmt->get_result();
+ $arrServicesNames = array();
+ $arrServicesId = array();
+ while ($result = $query->fetch_assoc()) {
+ $arrServicesNames[] = $result['name'];
+ $arrServicesId[] = (int) $result['id'];
+ }
+ $this->status_id = $status_id;
+ $this->servicenames = implode(",", $arrServicesNames);
+ $this->serviceids = implode(",", $arrServicesId);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Loop over the list of subscribers to notify depending on impacted service(s) and
+ * call the differnet notification handles.
+ * @return void
+ */
+ public function notify_subscribers()
+ {
+ global $mysqli;
+ // Fetch list of unique subscribers for given service
+ // Direct inclusion of variable withour using prepare justified by the fact that
+ // this->serviceids are not user submitted
+ $sql = "SELECT DISTINCT subscriberIDFK FROM services_subscriber WHERE serviceIDFK IN (" . $this->serviceids . ")";
+ $query = $mysqli->query($sql);
+
+ while ($subscriber = $query->fetch_assoc()) {
+ // Fetch list of subscriber details for already found subscriber IDs
+ $stmt = $mysqli->prepare("SELECT typeID, userID, firstname, token FROM subscribers WHERE subscriberID = ? AND active=1");
+ $stmt->bind_param("i", $subscriber['subscriberIDFK']);
+ $stmt->execute();
+ $subscriberQuery = $stmt->get_result();
+
+ while ($subscriberData = $subscriberQuery->fetch_assoc()) {
+ $typeID = $subscriberData['typeID']; // Telegram = 1, email = 2
+ $userID = $subscriberData['userID'];
+ $firstname = $subscriberData['firstname'];
+ $token = $subscriberData['token'];
+
+ // Handle telegram
+ if ($typeID == 1) {
+ $this->submit_telegram($userID, $firstname, $token);
+ }
+
+ // Handle email
+ if ($typeID == 2) {
+ $this->submit_email($userID, $token);
+ }
+ }
+ }
+ }
+
+ /**
+ * Sends Telegram notification message using their web api.
+ * @param string $userID The Telegram userid to send to
+ * @param string $firstname The users firstname
+ * @param string $uthkey Token used for managing subscription
+ * @return void
+ */
+ public function submit_telegram($userID, $firstname, $token)
+ {
+ // TODO Handle limitations (Max 30 different subscribers per second)
+ // TODO Error handling
+ $msg = _("Hi %s!\nThere is a status update for service(s): %s\nThe new status is: %s\nTitle: %s\n\n%s\n\nView online");
+ $msg = sprintf($msg, $firstname, $this->servicenames, $this->status, $this->title, $this->text, WEB_URL);
+
+ $tg_message = urlencode($msg);
+ $response = json_decode(file_get_contents("https://api.telegram.org/bot" . TG_BOT_API_TOKEN . "/sendMessage?chat_id=" . $userID . "&parse_mode=HTML&text=" . $tg_message), true);
+ }
+
+ /**
+ * Sends email notifications to a subscriber.
+ * Function depends on Parsedown and Mailer class being loaded.
+ * @param String $userID The email address to send to
+ * @param String $uthkey Users token for managing subscription
+ * @return void
+ */
+ public function submit_email($userID, $token)
+ {
+ // TODO Error handling
+ $Parsedown = new Parsedown();
+ $mailer = new Mailer();
+
+ $str_mail = file_get_contents("../libs/templates/email_status_update.html");
+ $str_mail = str_replace("%name%", NAME, $str_mail);
+ // $smtp_mail = str_replace("%email%", $userID, $smtp_mail);
+ $str_mail = str_replace("%url%", WEB_URL, $str_mail);
+ $str_mail = str_replace("%service%", $this->servicenames, $str_mail);
+ $str_mail = str_replace("%status%", $this->status, $str_mail);
+ $str_mail = str_replace("%time%", date("c", $this->time), $str_mail);
+ $str_mail = str_replace("%comment%", $Parsedown->setBreaksEnabled(true)->text($this->text), $str_mail);
+ $str_mail = str_replace("%token%", $token, $str_mail);
+
+ $str_mail = str_replace("%service_status_update_from%", _("Service status update from"), $str_mail);
+ $str_mail = str_replace("%services_impacted%", _("Service(s) Impacted"), $str_mail);
+ $str_mail = str_replace("%status_label%", _("Status"), $str_mail);
+ $str_mail = str_replace("%time_label%", _("Time"), $str_mail);
+ $str_mail = str_replace("%manage_subscription%", _("Manage subscription"), $str_mail);
+ $str_mail = str_replace("%unsubscribe%", _("Unsubscribe"), $str_mail);
+ $str_mail = str_replace("%powered_by%", _("Powered by"), $str_mail);
+ $subject = _('Status update from') . ' - ' . NAME . ' [ ' . $this->status . ' ]';
+ $mailer->send_mail($userID, $subject, $str_mail);
+ }
+}
\ No newline at end of file
diff --git a/classes/subscriber.php b/classes/subscriber.php
new file mode 100644
index 0000000..bdc8b92
--- /dev/null
+++ b/classes/subscriber.php
@@ -0,0 +1,328 @@
+firstname = null;
+ $this->lastname = null;
+ $this->userID = "";
+ $this->token = null;
+ $this->active = 0;
+ $this->typeID = null;
+ }
+
+ /**
+ * Gets authentcation token for specified subscriberID
+ * @param Integer $subscriberID - specifies which subscriber we are looking up
+ * @param Integer $typeID - specifies which type of subscription we are refering (1 = telegram, 2 = email)
+ * @return String $token - 32 bytes HEX string
+ */
+ public function get_token($subscriberID, $typeID)
+ {
+ global $mysqli;
+ $stmt = $mysqli->prepare("SELECT token FROM subscribers WHERE subscriberID = ? and typeID=? and active = 1 LIMIT 1");
+ $stmt->bind_param("ii", $subscriberID, $typeID);
+ $stmt->execute();
+ $result = $stmt->get_result();
+ if ($result->num_rows > 0) {
+ $row = $result->fetch_assoc();
+ $this->token = $row['token'];
+ //$this->get_subscriber_by_token($this->token);
+ return $row['token'];
+ }
+ return false;
+
+ }
+ public function get_subscriber_by_token($token)
+ {
+ global $mysqli;
+ $stmt = $mysqli->prepare("SELECT subscriberID FROM subscribers WHERE token=? and typeID=?");
+ $stmt->bind_param("si", $token, $this->typeID);
+ $stmt->execute();
+ $result = $stmt->get_result();
+ if ($result->num_rows > 0) {
+ $row = $result->fetch_assoc();
+ $this->id = $row['subscriberID'];
+ $this->populate(); //
+ return true;
+ }
+ return false;
+ }
+
+ public function get_subscriber_by_userid($create = false)
+ {
+ global $mysqli;
+ $stmt = $mysqli->prepare("SELECT subscriberID FROM subscribers WHERE userID LIKE ? AND typeID = ? LIMIT 1");
+ $stmt->bind_param("si", $this->userID, $this->typeID );
+ $stmt->execute();
+ $result = $stmt->get_result();
+
+ if ($result->num_rows > 0) {
+ $row = $result->fetch_assoc();
+ $this->id = $row['subscriberID'];
+ $this->populate();
+ return $row['subscriberID'];
+ } else {
+ // User is not registered in DB, so add if $create = true
+ if ( $create ) {
+ $subscriber_id = $this->add($this->typeID, $this->userID, $this->active, $this->firstname, $this->lastname);
+ return $subscriber_id;
+ }
+ return false;
+ }
+ }
+
+ public function populate()
+ {
+ global $mysqli;
+ $stmt = $mysqli->prepare("SELECT typeID, userID, firstname, lastname, token, active FROM subscribers WHERE subscriberID = ?");
+ $stmt->bind_param("i", $this->id);
+ $stmt->execute();
+ $result = $stmt->get_result();
+ if ($result->num_rows > 0) {
+ $row = $result->fetch_assoc();
+ $this->userID = $row['userID'];
+ $this->typeID = $row['typeID'];
+ $this->firstname = $row['firstname'];
+ $this->lastname = $row['lastname'];
+ $this->token = $row['token'];
+ $this->active = $row['active'];
+ return true;
+ }
+ return false;
+ }
+
+ public function add($typeID, $userID, $active = null, $firstname = null, $lastname = null)
+ {
+ global $mysqli;
+ $expireTime = strtotime("+2 hours");
+ $updateTime = strtotime("now");
+ $token = $this->generate_token();
+ syslog(1,"token". $token);
+ $stmt = $mysqli->prepare("INSERT INTO subscribers (typeID, userID, firstname, lastname, token, active, expires, create_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
+ $stmt->bind_param("issssiii", $typeID, $userID, $firstname, $lastname, $token, $active, $expireTime, $updateTime);
+ $stmt->execute();
+ $query = $stmt->get_result();
+
+ $this->id = $mysqli->insert_id;
+ $this->typeID = $typeID;
+ $this->userID = $userID;
+ $this->token = $token;
+ $this->firstname = $firstname;
+ $this->lastname = $lastname;
+ $this->active = $active;
+ return $this->id;
+ }
+
+ public function update($subscriberID)
+ {
+ global $mysqli;
+ $updateTime = strtotime("now");
+ $stmt = $mysqli->prepare("UPDATE subscribers SET update_time = ? WHERE subscriberID=?");
+ $stmt->bind_param("ii", $updateTime, $subscriberId);
+ $stmt->execute();
+ return true;
+
+ }
+
+ public function activate($subscriberID)
+ {
+ global $mysqli;
+ $updateTime = strtotime("now");
+
+ $stmt = $mysqli->prepare("UPDATE subscribers SET update_time = ?, expires = ? WHERE subscriberID = ?");
+ $tmp = null;
+ $stmt->bind_param("iii", $updateTime, $tmp, $subscriberId);
+ $stmt->execute();
+ return true;
+ }
+
+ public function delete($id)
+ {
+ global $mysqli;
+
+ $stmt = $mysqli->prepare("DELETE FROM services_subscriber WHERE subscriberIDFK = ?");
+ $stmt->bind_param("i", $this->id);
+ $stmt->execute();
+ $query = $stmt->get_result();
+
+ $stmt = $mysqli->prepare("DELETE FROM subscribers WHERE subscriberID = ?");
+ $stmt->bind_param("i", $this->id);
+ $stmt->execute();
+ $query = $stmt->get_result();
+
+ }
+
+ public function check_userid_exist()
+ {
+ global $mysqli;
+
+ $stmt = $mysqli->prepare("SELECT subscriberID, userID, token, active FROM subscribers WHERE typeID=? AND userID=? LIMIT 1");
+
+ $stmt->bind_param("is", $this->typeID, $this->userID);
+ $stmt->execute();
+ $result = $stmt->get_result();
+
+ if($result->num_rows > 0) {
+ $row = $result->fetch_assoc();
+ $this->id = $row['subscriberID'];
+ $this->populate();
+ return true;
+ }
+ return false;
+ }
+
+ public function is_active_subscriber($token)
+ {
+ global $mysqli;
+
+
+ $stmt = $mysqli->prepare("SELECT subscriberID, token, userID, active, expires FROM subscribers WHERE token LIKE ? LIMIT 1");
+ $stmt->bind_param("s", $token );
+ $stmt->execute();
+ $result = $stmt->get_result();
+
+ if ($result->num_rows > 0) {
+ $row = $result->fetch_assoc();
+ } else {
+ // No data found, fail gently...
+ return false;
+ }
+
+ // If account is not already active, check if we are within timeframe of exipre +2h
+ // and active if so, otherwise,delete account and return falsev
+ if ( $row['active'] <> 1 ) {
+
+ // Calculate time range for when subscription need to be validated
+ $time_end = $row['expires'];
+ $time_start = $time_end - (3600*2); // TODO - make this interval configurable via a config option
+ $time_now = time();
+
+ if ( ($time_now > $time_start) && ($time_now < $time_end) ) {
+ // Timefram is within range, active user..
+ $stmt2 = $mysqli->prepare("UPDATE subscribers SET active=1, expires=null WHERE subscriberID = ?");
+ $stmt2->bind_param("i", $row['subscriberID']);
+ $stmt2->execute();
+ $result = $stmt2->get_result();
+ $this->active = 1;
+ $this->id = $row['subscriberID'];
+ $this->userID = $row['userID'];
+ $this->token = $row['token'];
+ return true;
+
+ } else {
+ // Timeframe outside of given scope -> delete account
+ $stmt2 = $mysqli->prepare("DELETE FROM subscribers WHERE subscriberID = ?");
+ $stmt2->bind_param("i", $row['subscriberID']);
+ $stmt2->execute();
+ $result = $stmt2->get_result();
+ $this->active = 0;
+ return false;
+ }
+ }
+
+ // if we get here, account should already be active
+ $this->active = 1;
+ $this->id = $row['subscriberID'];
+ $this->userID = $row['userID'];
+ $this->token = $row['token'];
+ return true;
+ }
+
+ /**
+ * Generate a new 64 byte token (32 bytes converted from bin2hex = 64 bytes)
+ * @return string token
+ */
+ public function generate_token()
+ {
+ global $mysqli;
+
+ if ( function_exists('openssl_random_pseudo_bytes') ) {
+ $token = openssl_random_pseudo_bytes(32); //Generate a random string.
+ $token = bin2hex($token); //Convert the binary data into hexadecimal representation.
+ } else {
+ // Use alternative token generator if openssl isn't available...
+ $token = make_alt_token(32, 32);
+ }
+
+ // Make sure token doesn't already exist in db
+ $stmt = $mysqli->prepare("SELECT subscriberID FROM subscribers WHERE token LIKE ?");
+ echo $mysqli->error;
+ $stmt->bind_param("s", $token);
+ $stmt->execute();
+ $result = $stmt->get_result();
+ if ($result->num_rows > 0 ) {
+ // token already exists, call self again
+ $token = $this->generate_token();
+ }
+
+ return $token;
+ }
+
+ /**
+ * Alternative token generator if openssl_random_pseudo_bytes is not available
+ * Original code by jsheets at shadonet dot com from http://php.net/manual/en/function.mt-rand.php
+ * @params int min_length Minimum length of token
+ * @params int max_length Maximum length of token
+ * @return String token
+ */
+ public function make_alt_token($min_length = 32, $max_length = 64)
+ {
+ $key = '';
+
+ // build range and shuffle range using ASCII table
+ for ($i=0; $i<=255; $i++) {
+ $range[] = chr($i);
+ }
+
+ // shuffle our range 3 times
+ for ($i=0; $i<=3; $i++) {
+ shuffle($range);
+ }
+
+ // loop for random number generation
+ for ($i = 0; $i < mt_rand($min_length, $max_length); $i++) {
+ $key .= $range[mt_rand(0, count($range)-1)];
+ }
+
+ $return = bin2hex($key);
+
+ if (!empty($return)) {
+ return $return;
+ } else {
+ return 0;
+ }
+ }
+
+ public function set_logged_in()
+ {
+ $_SESSION['subscriber_valid'] = true;
+ $_SESSION['subscriber_id'] = $this->id;
+ $_SESSION['subscriber_userid'] = $this->userID;
+ $_SESSION['subscriber_typeid'] = $this->typeID; //email
+ $_SESSION['subscriber_token'] = $this->token;
+ }
+
+ public function set_logged_off()
+ {
+ unset($_SESSION['subscriber_valid']);
+ unset($_SESSION['subscriber_userid']);
+ unset($_SESSION['subscriber_typeid']);
+ unset($_SESSION['subscriber_id']);
+ unset($_SESSION['subscriber_token']);
+ }
+
+}
\ No newline at end of file
diff --git a/classes/subscriptions.php b/classes/subscriptions.php
new file mode 100644
index 0000000..d1cf6e4
--- /dev/null
+++ b/classes/subscriptions.php
@@ -0,0 +1,94 @@
+prepare("INSERT INTO services_subscriber (subscriberIDFK, serviceIDFK) VALUES (?, ?)");
+ $stmt->bind_param("ii", $userID, $service);
+ $stmt->execute();
+ $query = $stmt->get_result();
+ }
+
+ public function remove($userID, $service)
+ {
+ global $mysqli;
+
+ $stmt = $mysqli->prepare("DELETE FROM services_subscriber WHERE subscriberIDFK = ? AND serviceIDFK = ?");
+ $stmt->bind_param("ii", $userID, $service);
+ $stmt->execute();
+ $query = $stmt->get_result();
+ }
+
+ function render_subscribed_services($typeID, $subscriberID, $userID, $token)
+ {
+ global $mysqli;
+ $stmt = $mysqli->prepare("SELECT services.id, services.name, subscribers.subscriberID, subscribers.userID, subscribers.token
+ FROM services
+ LEFT JOIN services_subscriber ON services_subscriber.serviceIDFK = services.id
+ LEFT JOIN subscribers ON services_subscriber.subscriberIDFK = subscribers.subscriberID
+ WHERE subscribers.typeID = ? AND subscribers.subscriberID = ?");
+ $stmt->bind_param("ii", $typeID, $subscriberID);
+ $stmt->execute();
+ $query = $stmt->get_result();
+
+ $timestamp = time();
+
+ $strNotifyType = _('E-mail Notification subscription');
+ if ( $typeID == 1 ) { $strNotifyType = _('Telegram Notification subscription'); }
+
+ ?>
+