Просмотр исходного кода

Chat Beta and Plex Cache and Email Changes

Added more to Chat Beta which inlcude who is online and pop up notifications
Added error messages to failed emails
Added Plex Image cache to homepage
Other minor edits
causefx 8 лет назад
Родитель
Сommit
23cd8ff57f
15 измененных файлов с 811 добавлено и 58 удалено
  1. 42 10
      chat.php
  2. 26 0
      chat/getonline.php
  3. 2 0
      chat/logmessage.php
  4. 2 2
      chat/refreshmessages.php
  5. 86 27
      chatjs.php
  6. 1 0
      check.php
  7. 1 0
      config/configDefaults.php
  8. 1 1
      css/style.css
  9. 38 3
      functions.php
  10. 7 7
      homepage.php
  11. 3 3
      index.php
  12. 581 0
      js/push.js
  13. 11 1
      settings.php
  14. 9 3
      user.php
  15. 1 1
      vendor/phpmailer/phpmailer/class.phpmailer.php

+ 42 - 10
chat.php

@@ -136,6 +136,20 @@ endif; ?>
         
             $dbcreated = false;
         
+            if (!extension_loaded("SQLITE3")){ 
+
+                echo '<div class="row" style="margin: 0"><div class="panel panel-danger" style="margin: 10px";>';
+                echo '<div class="panel-heading">';
+                echo '<h3 class="panel-title">SQLITE3</h3>';
+                echo '</div>';
+                echo '<div style="color: gray" class="panel-body">';
+                echo 'SQLITE3 is NOT loaded!  Please install it before proceeding';
+
+                echo '</div></div></div>';
+                die();
+
+            }  
+        
             if( $db = new SQLite3("chatpack.db") )
             {
                 if( $db->busyTimeout(5000) )
@@ -156,8 +170,14 @@ endif; ?>
                                           (id INTEGER PRIMARY KEY,
                                           timestamp INTEGER NOT NULL,
                                           user TEXT NOT NULL)";
+                            
+                            $onlinetable = "CREATE TABLE IF NOT EXISTS chatpack_last_message
+                                          (
+                                          user TEXT PRIMARY KEY NOT NULL,
+                                          timestamp INTEGER NOT NULL,
+                                          avatar TEXT NOT NULL)";
 
-                            if( $db->exec($usertable) )
+                            if( $db->exec($usertable) && $db->exec($onlinetable) )
                             {
                                 $dbcreated = true;
                             }
@@ -200,28 +220,35 @@ endif; ?>
             <div id="content" class="container-fluid">
                 <br>
                 <div class="row">
-                    <div class="col-lg-12">
-                        <div class="content-box big-box chat">
-                            <div class="content-title i-block">
-                                <h4 class="zero-m">Welcome To The Chat <?php echo $USER->username;?></h4>
-                            </div>
-                            <div class="box" style="overflow: hidden; width: auto; height: calc(100vh - 130px);">
+                    <div class="col-lg-10">
+                        <div class="content-box big-box chat gray-bg">
+                            <div class="box" style="overflow: hidden; width: auto; height: 550px;">
                                 <div id="intro">
-                                    <center><img class="logo" alt="logo" src="images/organizr-logo-h-d.png">
+                                    <center><img class="logo" alt="logo" src="images/organizr-logo-h.png">
                                     <br><br>start chatting...</center>
                                 </div>
                                 <ul id="messages" class="chat-double chat-container"></ul>
                                 <ul class="chat-double chat-container" style="padding: 0px;"><li id="istyping"></li></ul>
                             </div>
-            
-                            <input id="message" autofocus type="text" class="form-control" placeholder="Enter your text" autocomplete="off"/>
+                            <br/>
+                            <input id="message" autofocus type="text" class="form-control gray-bg" placeholder="Enter your text" autocomplete="off"/>
                             <audio id="tabalert" preload="auto">
                                 <source src="chat/audio/newmessage.mp3" type="audio/mpeg">
                             </audio>
 
                         </div>
                     </div>
+                    <div class="col-lg-2">
+                        <div class="content-box">
+                            <div class="content-title big-box i-block gray-bg">
+                                <h4 class="zero-m">Online</h4>
+                            </div>
+                            <div class="clearfix"></div>
+                            <div id="onlineusers" class="big-box"></div>
+                        </div>
+                    </div>
                 </div>
+
             </div>    
         </div>
         
@@ -246,6 +273,11 @@ endif; ?>
             scrollspeed: 30,
             mousescrollstep: 60
         });
+        $("#onlineusers").niceScroll({
+            railpadding: {top:0,right:0,left:0,bottom:0},
+            scrollspeed: 30,
+            mousescrollstep: 60
+        });
     </script>
 
 </html>

+ 26 - 0
chat/getonline.php

@@ -0,0 +1,26 @@
+<?php
+
+include("connect.php");
+
+$onlineusers = array();
+
+if( $result = $db->query("SELECT user, timestamp, avatar FROM chatpack_last_message ORDER BY user ASC") )
+{
+    while( $row = $result->fetchArray() )
+    {            
+        $user = $row["user"];
+        $timestamp = $row["timestamp"];
+        $avatar = $row["avatar"];
+        $push = array($user, $timestamp, $avatar);
+
+        array_push($onlineusers, $push);
+    }
+}
+
+$db->close();
+
+// pass online users as JSON back to chat.js
+
+echo json_encode($onlineusers);
+
+?>

+ 2 - 0
chat/logmessage.php

@@ -22,6 +22,8 @@ if( strlen($message) > 0 )
 
     $db->exec("INSERT INTO chatpack_log (timestamp, user, avatar, message)
                VALUES ('$timestamp', '$user', '$avatar', '$message')");
+    $db->exec("REPLACE INTO chatpack_last_message (timestamp, user, avatar)
+               VALUES ('$timestamp', '$user', '$avatar')");
 }
 
 function encryptmessage($msg)

+ 2 - 2
chat/refreshmessages.php

@@ -8,7 +8,7 @@ include("connect.php");
 
 if( $result = $db->query("SELECT * FROM
                          (SELECT id, timestamp, user, avatar, message
-                          FROM chatpack_log ORDER BY id DESC LIMIT 250)
+                          FROM chatpack_log ORDER BY id DESC LIMIT 125)
                           ORDER BY id ASC") )
 {   
     $newmessages = array();
@@ -22,7 +22,7 @@ if( $result = $db->query("SELECT * FROM
         $message = $row["message"];
         
         $timenow = time();
-        $messagetime = date("h:i", intval($timestamp));
+        $messagetime = date("h:iA", intval($timestamp));
         $messagedate = date("m-d", intval($timestamp));
         $message = utf8_encode($message);
         

+ 86 - 27
chatjs.php

@@ -354,12 +354,7 @@ $(document).ready(function()
     
     // allowed characters in message
     
-    $("#message").keyup(function()
-    {
-        var text = $(this).val();
-        $(this).val(text.replace("'", "`")
-                        .replace("###", "#"));
-    });
+
     
     // log message
     
@@ -497,30 +492,79 @@ $(document).ready(function()
                     // check who is still online
                                     
                     var datetoday = new Date();
-                    var timenow = datetoday.getTime() / 1000;   
+                    var timenow = datetoday.getTime() / 1000;  
 
-                    $(".img-circle").each(function()
+                    $.ajax
+                ({
+                    url: "chat/getonline.php",
+                    cache: false,
+                    success: function(result)
                     {
-                        var timestamp = this.id;
-                        var avauser = $(this).attr("alt");
-                        var avatarsrc = $(this).attr("src");
+                        var onlineusers = JSON.parse(result);
+                        var oldonlineusers = $("#onlineusers").html();
+                        var newonlineusers = '';
                         
-                        // set user offline avatar
-
-                        if( timestamp < timenow - 2700 )
+                        if( onlineusers.length <= 0 )  // no user typing
                         {
-                            $(this).addClass("offline");
-                            $(this).removeClass("online");
-                            
+                            newonlineusers += "No Users Online";
                         }
-                        else  // set user online avatar
-                        {
+                        else
+                        {   
+                            if( onlineusers.length >= 1 )  // one user typing
+                            {
+                                jQuery.each( onlineusers, function( i, val ) {
+                                    var timecheck = val[1];
+                                    var status = "";
+                                    var color = "";
+                                    if( timecheck < timenow - 1800 )
+                                    {
+                                        status = '<span style="min-height: 14px;margin-top: 10px;" class="pull-right badge badge-danger animated pulse"> </span>';
+                                        color = "red";
+                                    }else{
+                                        status = '<span style="min-height: 14px;margin-top: 10px;" class="pull-right badge badge-success animated flash"> </span>';                       
+                                        color = "blue";                       
+                                    }
+                                    if( timecheck < timenow - 3600 )
+                                    {
+                                        newonlineusers += '';      
+                                    }else{
+                                        newonlineusers += '<div class="member-info"><img style="height:40px" src="'+val[2]+'" alt="admin" class="img-circle"><span class="member-name">'+val[0]+'</span>'+status+'</div>'; 
+                                        i++;
+                                    }
+                                    
+                                    $("img[alt^='"+val[0]+"']").each(function()
+                                    {
+                                        var timestamp = val[1];
 
-                            $(this).addClass("online");
-                            $(this).removeClass("offline");
-                           
+                                        // set user offline avatar
+
+                                        if( timestamp < timenow - 1800 )
+                                        {
+                                            $(this).addClass("offline");
+                                            $(this).removeClass("online");
+
+                                        }
+                                        else  // set user online avatar
+                                        {
+
+                                            $(this).addClass("online");
+                                            $(this).removeClass("offline");
+
+                                        }
+                                    });
+                                    
+                                });
+                            }
                         }
-                    });
+                        if(newonlineusers === ''){ newonlineusers = "No Users Online";}
+                        if( newonlineusers != oldonlineusers )
+                        {
+                            $("#onlineusers").html(newonlineusers);
+                        }
+                    }
+                });
+
+                    
                     
                     // new messages
                     
@@ -559,7 +603,7 @@ $(document).ready(function()
 
                                     if( message.lastIndexOf(userwriting) == -1 )
                                     {
-                                        newmessagealert();
+                                        newmessagealert(message);
                                     }
                                     
                                     // refresh eventlisteners of messages to set likes
@@ -637,7 +681,7 @@ $(document).ready(function()
     window.onfocus = function()
     {
         tabinfocus = true;
-        parent.document.title = "Organzir Chat";
+        parent.document.title = "Chat";
         window.parent.$("span[id^='chat.phps']").html("");
     };
     
@@ -648,7 +692,7 @@ $(document).ready(function()
     
     // new message tab alert
     
-    function newmessagealert()
+    function newmessagealert(message)
     {   
         if( !tabinfocus )
         {
@@ -660,9 +704,24 @@ $(document).ready(function()
                 i++
              }
 
-            parent.document.title = i + " Organzir Chat";
+            parent.document.title = i + " Chat";
             //window.parent.$("#chat.phpx").addClass("gottem");
             window.parent.$("span[id^='chat.phps']").html(i);
+            var $jQueryObject = $($.parseHTML(message));
+            var alertMessage = $jQueryObject.find(".chat-body").html();
+            var alertUsername = $jQueryObject.find("h4[class^='pull-left zero-m']").html();
+            var alertIcon = $jQueryObject.find("img").attr("src");;
+            if(isMobile === false){
+                parent.Push.create(alertUsername, {
+                    body: alertMessage,
+                    icon: alertIcon,
+                    timeout: 4000,
+                    onClick: function () {
+                        window.parent.focus();
+                        this.close();
+                    }
+                });
+            }
                         
             // sound
                         

+ 1 - 0
check.php

@@ -162,6 +162,7 @@ $folder = USER_HOME;
                 
                 check("PDO_SQLITE");
                 check("PDO");
+                check("SQLITE3");
                 check("Zip");
                 check("openssl");
                 check("session");

+ 1 - 0
config/configDefaults.php

@@ -72,4 +72,5 @@ return array(
 	"git_branch" => "master",
 	"git_check" => true,
     "speedTest" => false,
+    "smtpHostType" => "tls",
 );

+ 1 - 1
css/style.css

@@ -3265,7 +3265,7 @@ ul.inbox-pagination li {
   /*padding: 22px;*/
   padding: 19px 22px 19px 22px;
   line-height: 1.4;
-  z-index: 4000;
+  z-index: 10000;
   pointer-events: none;
   color: #fff;
   font-size: 80%;

+ 38 - 3
functions.php

@@ -2,7 +2,7 @@
 
 // ===================================
 // Define Version
- define('INSTALLEDVERSION', '1.36');
+ define('INSTALLEDVERSION', '1.37');
 // ===================================
 
 // Debugging output functions
@@ -490,6 +490,7 @@ function resolvePlexItem($server, $token, $item) {
 			$image = 'carousel-image season';
 			$style = '';
             $thumb = $item['thumb'];
+            $key = $item['ratingKey'];
 			break;
         case 'episode':
 			$title = $item['grandparentTitle'];
@@ -498,6 +499,16 @@ function resolvePlexItem($server, $token, $item) {
 			$image = 'carousel-image season';
 			$style = '';
             $thumb = $item['parentThumb'];
+            $key = $item['ratingKey'];
+			break;
+        case 'clip':
+			$title = $item['title'];
+			$summary = $item['summary'];
+			$width = 100;
+			$image = 'carousel-image movie';
+			$style = '';
+            $thumb = $item['thumb'];
+            $key = "clip";
 			break;
 		case 'album':
 		case 'track':
@@ -507,6 +518,7 @@ function resolvePlexItem($server, $token, $item) {
 			$image = 'album';
 			$style = 'left: 160px !important;';
             $thumb = $item['thumb'];
+            $key = $item['ratingKey'];
 			break;
 		default:
 			$title = $item['title'];
@@ -515,15 +527,20 @@ function resolvePlexItem($server, $token, $item) {
 			$image = 'carousel-image movie';
 			$style = '';
             $thumb = $item['thumb'];
+            $key = $item['ratingKey'];
 	}
 	
 	// If No Overview
 	if (!isset($itemDetails['Overview'])) {
 		$itemDetails['Overview'] = '';
 	}
+    $image_url = 'ajax.php?a=plex-image&img='.$thumb.'&height='.$height.'&width='.$width.'&key='.$key.'';
+    if (file_exists('images/cache/'.$key.'.jpg')){
+        $image_url = 'images/cache/'.$key.'.jpg';
+    }
 	
 	// Assemble Item And Cache Into Array 
-	return '<div class="item"><a href="'.$address.'" target="_blank"><img alt="'.$item['Name'].'" class="'.$image.'" src="ajax.php?a=plex-image&img='.$thumb.'&height='.$height.'&width='.$width.'"></a><div class="carousel-caption" style="'.$style.'"><h4>'.$title.'</h4><small><em>'.$summary.'</em></small></div></div>';
+	return '<div class="item"><a href="'.$address.'" target="_blank"><img alt="'.$item['Name'].'" class="'.$image.'" src="'.$image_url.'"></a><div class="carousel-caption" style="'.$style.'"><h4>'.$title.'</h4><small><em>'.$summary.'</em></small></div></div>';
 }
 
 // Create Carousel
@@ -726,15 +743,33 @@ function getEmbyImage() {
 // Get Image From Plex
 function getPlexImage() {
 	$plexAddress = qualifyURL(PLEXURL);
+    if (!file_exists('images/cache')) {
+        mkdir('images/cache', 0777, true);
+    }
 	
 	$image_url = $_GET['img'];
+	$key = $_GET['key'];
 	$image_height = $_GET['height'];
 	$image_width = $_GET['width'];
 	
 	if(isset($image_url) && isset($image_height) && isset($image_width)) {
 		$image_src = $plexAddress . '/photo/:/transcode?height='.$image_height.'&width='.$image_width.'&upscale=1&url=' . $image_url . '&X-Plex-Token=' . PLEXTOKEN;
-		header('Content-type: image/jpeg');
+        $cachefile = 'images/cache/'.$key.'.jpg';
+        $cachetime = 604800;
+        // Serve from the cache if it is younger than $cachetime
+        if (file_exists($cachefile) && time() - $cachetime < filemtime($cachefile)) {
+            header("Content-type: image/jpeg");
+            @readfile($cachefile);
+            exit;
+        }
+		ob_start(); // Start the output buffer
+        header('Content-type: image/jpeg');
 		@readfile($image_src);
+        // Cache the output to a file
+        $fp = fopen($cachefile, 'wb');
+        fwrite($fp, ob_get_contents());
+        fclose($fp);
+        ob_end_flush(); // Send the output to the browser
 		die();
 	} else {
 		echo "Invalid Plex Request";	

+ 7 - 7
homepage.php

@@ -262,9 +262,9 @@ endif; ?>
                             var ul = document.getElementById('upload')
                             var ping = document.getElementById('ping')
                             var jitter = document.getElementById('jitter')
-                            dl.className = status === 1 ? 'w-amount flash' : 'w-amount'
-                            ping.className = status === 2 ? 'w-amount flash' : 'w-amount'
-                            jitter.className = ul.className = status === 3 ? 'w-amount flash' : 'w-amount'
+                            dl.className = status === 1 ? 'w-name flash' : 'w-name'
+                            ping.className = status === 2 ? 'w-name flash' : 'w-name'
+                            jitter.className = ul.className = status === 3 ? 'w-name flash' : 'w-name'
                             if (status >= 4) {
                                 clearInterval(interval)
                                 document.getElementById('abortBtn').style.display = 'none'
@@ -300,7 +300,7 @@ endif; ?>
                                 <div class="w-descr left pull-left text-center">
                                     <span class="testName text-uppercase w-name">Download</span>
                                     <br>
-                                    <span class="w-amount counter" id="download" ></span>
+                                    <span class="w-name counter" id="download" ></span>
                                 </div>
                             </div>
                         </div>
@@ -314,7 +314,7 @@ endif; ?>
                                 <div class="w-descr left pull-left text-center">
                                     <span class="testName text-uppercase w-name">Upload</span>
                                     <br>
-                                    <span class="w-amount counter" id="upload" ></span>
+                                    <span class="w-name counter" id="upload" ></span>
                                 </div>
                             </div>
                         </div>
@@ -328,7 +328,7 @@ endif; ?>
                                 <div class="w-descr left pull-left text-center">
                                     <span class="testName text-uppercase w-name">Latency</span>
                                     <br>
-                                    <span class="w-amount counter" id="ping" ></span>
+                                    <span class="w-name counter" id="ping" ></span>
                                 </div>
                             </div>
                         </div>
@@ -342,7 +342,7 @@ endif; ?>
                                 <div class="w-descr left pull-left text-center">
                                     <span class="testName text-uppercase w-name">Jitter</span>
                                     <br>
-                                    <span class="w-amount counter" id="jitter" ></span>
+                                    <span class="w-name counter" id="jitter" ></span>
                                 </div>
                             </div>
                         </div>

+ 3 - 3
index.php

@@ -265,7 +265,7 @@ if(file_exists("images/settings2.png")) : $iconRotate = "false"; $settingsIcon =
         <meta name="theme-color" content="#2d89ef">
         <link rel="stylesheet" type="text/css" href="css/addtohomescreen.css">
         <script src="js/addtohomescreen.js"></script>
-        <script src="push.js"></script>
+        <script src="js/push.js"></script>
 		<!--Other-->
 		<script src="js/ajax.js?v=<?php echo INSTALLEDVERSION; ?>"></script>
         <!--[if lt IE 9]>
@@ -1427,7 +1427,7 @@ endif; ?>
 
                 currentframe.attr("class", "iframe active");
                 document.title = thistitle;
-                window.location.href = '#' + thisname;
+                //window.location.href = '#' + thisname;
                 setHeight();
 
                 $("li[class^='tab-item active']").attr("class", "tab-item");
@@ -1444,7 +1444,7 @@ endif; ?>
 
                     $( '<div class="iframe active" data-content-url="'+thisid+'"><iframe scrolling="auto" sandbox="allow-forms allow-same-origin allow-pointer-lock allow-scripts allow-popups allow-modals allow-top-navigation" allowfullscreen="true" webkitallowfullscreen="true" frameborder="0" style="width:100%; height:100%; position: absolute;" src="'+thisid+'"></iframe></div>' ).appendTo( "#content" );
                     document.title = thistitle;
-                    window.location.href = '#' + thisname;
+                   // window.location.href = '#' + thisname;
 
                     setHeight();
 

+ 581 - 0
js/push.js

@@ -0,0 +1,581 @@
+/**
+ * Push
+ * =======
+ * A compact, cross-browser solution for the JavaScript Notifications API
+ *
+ * Credits
+ * -------
+ * Tsvetan Tsvetkov (ttsvetko)
+ * Alex Gibson (alexgibson)
+ *
+ * License
+ * -------
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015-2017 Tyler Nickerson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @preserve
+ */
+
+(function (global, factory) {
+
+    'use strict';
+
+    /* Use AMD */
+    if (typeof define === 'function' && define.amd) {
+        define(function () {
+            return new (factory(global, global.document))();
+        });
+    }
+    /* Use CommonJS */
+    else if (typeof module !== 'undefined' && module.exports) {
+        module.exports = new (factory(global, global.document))();
+    }
+    /* Use Browser */
+    else {
+        global.Push = new (factory(global, global.document))();
+    }
+
+})(typeof window !== 'undefined' ? window : this, function (w, d) {
+
+    var Push = function () {
+
+        /**********************
+            Local Variables
+        /**********************/
+
+        var
+        self = this,
+        isUndefined   = function (obj) { return obj === undefined; },
+        isString   = function (obj) { return typeof obj === 'string' },
+        isFunction = function (obj) { return obj && {}.toString.call(obj) === '[object Function]'; },
+
+        /* ID to use for new notifications */
+        currentId = 0,
+
+        /* Message to show if there is no suport to Push Notifications */
+        incompatibilityErrorMessage = 'PushError: push.js is incompatible with browser.',
+
+        /* Map of open notifications */
+        notifications = {},
+
+        /* Testing variable for the last service worker path used */
+        lastWorkerPath = null,
+
+        /**********************
+            Helper Functions
+        /**********************/
+
+        /**
+         * Closes a notification
+         * @param {Notification} notification
+         * @return {Boolean} boolean denoting whether the operation was successful
+         */
+        closeNotification = function (id) {
+            var errored = false,
+                notification = notifications[id];
+
+            if (typeof notification !== 'undefined') {
+                /* Safari 6+, Chrome 23+ */
+                if (notification.close) {
+                    notification.close();
+                /* Legacy webkit browsers */
+                } else if (notification.cancel) {
+                    notification.cancel();
+                /* IE9+ */
+                } else if (w.external && w.external.msIsSiteMode) {
+                    w.external.msSiteModeClearIconOverlay();
+                } else {
+                    errored = true;
+                    throw new Error('Unable to close notification: unknown interface');
+                }
+
+                if (!errored) {
+                    return removeNotification(id);
+                }
+            }
+
+            return false;
+        },
+
+        /**
+         * Adds a notification to the global dictionary of notifications
+         * @param {Notification} notification
+         * @return {Integer} Dictionary key of the notification
+         */
+        addNotification = function (notification) {
+            var id = currentId;
+            notifications[id] = notification;
+            currentId++;
+            return id;
+        },
+
+        /**
+         * Removes a notification with the given ID
+         * @param  {Integer} id - Dictionary key/ID of the notification to remove
+         * @return {Boolean} boolean denoting success
+         */
+        removeNotification = function (id) {
+            var dict = {},
+                success = false,
+                key;
+
+            for (key in notifications) {
+                if (notifications.hasOwnProperty(key)) {
+                    if (key != id) {
+                        dict[key] = notifications[key];
+                    } else {
+                        // We're successful if we omit the given ID from the new array
+                        success = true;
+                    }
+                }
+            }
+            // Overwrite the current notifications dictionary with the filtered one
+            notifications = dict;
+            return success;
+        },
+
+        prepareNotification = function (id, options) {
+            var wrapper;
+
+            /* Wrapper used to get/close notification later on */
+            wrapper = {
+                get: function () {
+                    return notifications[id];
+                },
+
+                close: function () {
+                    closeNotification(id);
+                }
+            };
+
+            /* Autoclose timeout */
+            if (options.timeout) {
+                setTimeout(function () {
+                    wrapper.close();
+                }, options.timeout);
+            }
+
+            return wrapper;
+        },
+
+        /**
+         * Callback function for the 'create' method
+         * @return {void}
+         */
+        createCallback = function (title, options, resolve) {
+            var notification,
+                onClose;
+
+            /* Set empty settings if none are specified */
+            options = options || {};
+
+            /* Set the last service worker path for testing */
+            self.lastWorkerPath = options.serviceWorker || 'serviceWorker.js';
+
+            /* onClose event handler */
+            onClose = function (id) {
+                /* A bit redundant, but covers the cases when close() isn't explicitly called */
+                removeNotification(id);
+                if (isFunction(options.onClose)) {
+                    options.onClose.call(this, notification);
+                }
+            };
+
+            /* Safari 6+, Firefox 22+, Chrome 22+, Opera 25+ */
+            if (w.Notification) {
+                try {
+                    notification =  new w.Notification(
+                        title,
+                        {
+                            icon: (isString(options.icon) || isUndefined(options.icon)) ? options.icon : options.icon.x32,
+                            body: options.body,
+                            tag: options.tag,
+                            requireInteraction: options.requireInteraction,
+                            silent: options.silent
+                        }
+                    );
+                } catch (e) {
+                    if (w.navigator) {
+                        /* Register ServiceWorker using lastWorkerPath */
+                        w.navigator.serviceWorker.register(self.lastWorkerPath);
+                        w.navigator.serviceWorker.ready.then(function(registration) {
+                            var localData = {
+                                id: currentId,
+                                link: options.link,
+                                origin: document.location.href,
+                                onClick: (isFunction(options.onClick)) ? options.onClick.toString() : '',
+                                onClose: (isFunction(options.onClose)) ? options.onClose.toString() : ''
+                            };
+
+                            if (typeof options.data !== 'undefined' && options.data !== null)
+                                localData = Object.assign(localData, options.data);
+
+                            /* Show the notification */
+                            registration.showNotification(
+                                title,
+                                {
+                                    icon: options.icon,
+                                    body: options.body,
+                                    vibrate: options.vibrate,
+                                    tag: options.tag,
+                                    data: localData,
+                                    requireInteraction: options.requireInteraction
+                                }
+                            ).then(function() {
+                                var id;
+
+                                /* Find the most recent notification and add it to the global array */
+                                registration.getNotifications().then(function(notifications) {
+                                    id = addNotification(notifications[notifications.length - 1]);
+
+                                    /* Send an empty message so the ServiceWorker knows who the client is */
+                                    registration.active.postMessage('');
+
+                                    /* Listen for close requests from the ServiceWorker */
+                                    navigator.serviceWorker.addEventListener('message', function (event) {
+                                        var data = JSON.parse(event.data);
+
+                                        if (data.action === 'close' && Number.isInteger(data.id))
+                                            removeNotification(data.id);
+                                    });
+
+                                    resolve(prepareNotification(id, options));
+                                });
+                            });
+                        });
+                    }
+                }
+
+            /* Legacy webkit browsers */
+            } else if (w.webkitNotifications) {
+
+                notification = w.webkitNotifications.createNotification(
+                    options.icon,
+                    title,
+                    options.body
+                );
+
+                notification.show();
+
+            /* Firefox Mobile */
+            } else if (navigator.mozNotification) {
+
+                notification = navigator.mozNotification.createNotification(
+                    title,
+                    options.body,
+                    options.icon
+                );
+
+                notification.show();
+
+            /* IE9+ */
+            } else if (w.external && w.external.msIsSiteMode()) {
+
+                //Clear any previous notifications
+                w.external.msSiteModeClearIconOverlay();
+                w.external.msSiteModeSetIconOverlay(
+                    ((isString(options.icon) || isUndefined(options.icon))
+                    ? options.icon
+                    : options.icon.x16), title
+                );
+                w.external.msSiteModeActivate();
+
+                notification = {};
+            } else {
+                throw new Error('Unable to create notification: unknown interface');
+            }
+
+            if (typeof(notification) !== 'undefined') {
+                var id = addNotification(notification),
+                    wrapper = prepareNotification(id, options);
+
+                /* Notification callbacks */
+                if (isFunction(options.onShow))
+                    notification.addEventListener('show', options.onShow);
+
+                if (isFunction(options.onError))
+                    notification.addEventListener('error', options.onError);
+
+                if (isFunction(options.onClick))
+                    notification.addEventListener('click', options.onClick);
+
+                notification.addEventListener('close', function() {
+                    onClose(id);
+                });
+
+                notification.addEventListener('cancel', function() {
+                    onClose(id);
+                });
+
+                /* Return the wrapper so the user can call close() */
+                resolve(wrapper);
+            }
+
+            resolve({}); // By default, pass an empty wrapper
+        },
+
+        /**
+         * Permission types
+         * @enum {String}
+         */
+        Permission = {
+            DEFAULT: 'default',
+            GRANTED: 'granted',
+            DENIED: 'denied'
+        },
+
+        Permissions = [Permission.GRANTED, Permission.DEFAULT, Permission.DENIED];
+
+        /* Allow enums to be accessible from Push object */
+        self.Permission = Permission;
+
+        /*****************
+            Permissions
+        /*****************/
+
+        /**
+         * Requests permission for desktop notifications
+         * @param {Function} callback - Function to execute once permission is granted
+         * @return {void}
+         */
+        self.Permission.request = function (onGranted, onDenied) {
+            var existing = self.Permission.get();
+
+            /* Return if Push not supported */
+            if (!self.isSupported) {
+                throw new Error(incompatibilityErrorMessage);
+            }
+
+            /* Default callback */
+            callback = function (result) {
+                switch (result) {
+
+                    case self.Permission.GRANTED:
+                        if (onGranted) onGranted();
+                        break;
+
+                    case self.Permission.DENIED:
+                        if (onDenied) onDenied();
+                        break;
+
+                }
+
+            };
+
+            /* Permissions already set */
+            if (existing !== self.Permission.DEFAULT) {
+                callback(existing);
+            }
+            /* Safari 6+, Chrome 23+ */
+            else if (w.Notification && w.Notification.requestPermission) {
+                Notification.requestPermission(callback);
+            }
+            /* Legacy webkit browsers */
+            else if (w.webkitNotifications && w.webkitNotifications.checkPermission) {
+                w.webkitNotifications.requestPermission(callback);
+            } else {
+                throw new Error(incompatibilityErrorMessage);
+            }
+        };
+
+        /**
+         * Returns whether Push has been granted permission to run
+         * @return {Boolean}
+         */
+        self.Permission.has = function () {
+            return Permission.get() === Permission.GRANTED;
+        };
+
+        /**
+         * Gets the permission level
+         * @return {Permission} The permission level
+         */
+        self.Permission.get = function () {
+
+            var permission;
+
+            /* Return if Push not supported */
+            if (!self.isSupported) { throw new Error(incompatibilityErrorMessage); }
+
+            /* Safari 6+, Chrome 23+ */
+            if (w.Notification && w.Notification.permissionLevel) {
+                permission = w.Notification.permissionLevel;
+
+            /* Legacy webkit browsers */
+            } else if (w.webkitNotifications && w.webkitNotifications.checkPermission) {
+                permission = Permissions[w.webkitNotifications.checkPermission()];
+
+            /* Firefox 23+ */
+            } else if (w.Notification && w.Notification.permission) {
+                permission = w.Notification.permission;
+
+            /* Firefox Mobile */
+            } else if (navigator.mozNotification) {
+                permission = Permission.GRANTED;
+
+            /* IE9+ */
+            } else if (w.external && w.external.msIsSiteMode() !== undefined) {
+                permission = w.external.msIsSiteMode() ? Permission.GRANTED : Permission.DEFAULT;
+            } else {
+                throw new Error(incompatibilityErrorMessage);
+            }
+
+            return permission;
+
+        };
+
+        /*********************
+            Other Functions
+        /*********************/
+
+        /**
+         * Detects whether the user's browser supports notifications
+         * @return {Boolean}
+         */
+        self.isSupported = (function () {
+
+             var isSupported = false;
+
+             try {
+
+                 isSupported =
+
+                     /* Safari, Chrome */
+                     !!(w.Notification ||
+
+                     /* Chrome & ff-html5notifications plugin */
+                     w.webkitNotifications ||
+
+                     /* Firefox Mobile */
+                     navigator.mozNotification ||
+
+                     /* IE9+ */
+                     (w.external && w.external.msIsSiteMode() !== undefined));
+
+             } catch (e) {}
+
+             return isSupported;
+
+         })();
+
+         /**
+          * Creates and displays a new notification
+          * @param {Array} options
+          * @return {Promise}
+          */
+        self.create = function (title, options) {
+            var promiseCallback;
+
+            /* Fail if the browser is not supported */
+            if (!self.isSupported) {
+                throw new Error(incompatibilityErrorMessage);
+            }
+
+            /* Fail if no or an invalid title is provided */
+            if (!isString(title)) {
+                throw new Error('PushError: Title of notification must be a string');
+            }
+
+            /* Request permission if it isn't granted */
+            if (!self.Permission.has()) {
+                promiseCallback = function(resolve, reject) {
+                    self.Permission.request(function() {
+                        try {
+                            createCallback(title, options, resolve);
+                        } catch (e) {
+                            reject(e);
+                        }
+                    }, function() {
+                        reject("Permission request declined");
+                    });
+                };
+            } else {
+                promiseCallback = function(resolve, reject) {
+                    try {
+                        createCallback(title, options, resolve);
+                    } catch (e) {
+                        reject(e);
+                    }
+                };
+            }
+
+            return new Promise(promiseCallback);
+        };
+
+        /**
+         * Returns the notification count
+         * @return {Integer} The notification count
+         */
+        self.count = function () {
+            var count = 0,
+                key;
+
+            for (key in notifications)
+                count++;
+
+            return count;
+        },
+
+        /**
+         * Internal function that returns the path of the last service worker used
+         * For testing purposes only
+         * @return {String} The service worker path
+         */
+        self.__lastWorkerPath = function () {
+            return self.lastWorkerPath;
+        },
+
+        /**
+         * Closes a notification with the given tag
+         * @param {String} tag - Tag of the notification to close
+         * @return {Boolean} boolean denoting success
+         */
+        self.close = function (tag) {
+            var key;
+            for (key in notifications) {
+                notification = notifications[key];
+                /* Run only if the tags match */
+                if (notification.tag === tag) {
+                    /* Call the notification's close() method */
+                    return closeNotification(key);
+                }
+            }
+        };
+
+        /**
+         * Clears all notifications
+         * @return {void}
+         */
+        self.clear = function () {
+            var success = true;
+
+            for (key in notifications)
+                success = success && closeNotification(key);
+
+            return success;
+        };
+    };
+
+    return Push;
+
+});

+ 11 - 1
settings.php

@@ -350,7 +350,7 @@ endif; ?>
 <?php
 $dirname = "images/";
 $images = scandir($dirname);
-$ignore = Array(".", "..", "favicon/", "favicon", "._.DS_Store", ".DS_Store", "confused.png", "sowwy.png", "sort-btns", "loading.png", "titlelogo.png", "default.svg", "login.png", "themes", "nadaplaying.jpg", "organizr-logo-h-d.png", "organizr-logo-h.png");
+$ignore = Array(".", "..", "favicon", "cache", "._.DS_Store", ".DS_Store", "confused.png", "sowwy.png", "sort-btns", "loading.png", "titlelogo.png", "default.svg", "login.png", "themes", "nadaplaying.jpg", "organizr-logo-h-d.png", "organizr-logo-h.png");
 foreach($images as $curimg){
 	if(!in_array($curimg, $ignore)) { ?>
 												<div class="col-xs-2" style="width: 75px; height: 75px; padding-right: 0px;">    
@@ -1252,6 +1252,16 @@ echo buildSettings(
 						'name' => 'smtpHostSenderEmail',
 						'value' => SMTPHOSTSENDEREMAIL,
 					),
+                    array(
+						'type' => 'select',
+						'labelTranslate' => 'SMTP_HOST_AUTH',
+						'name' => 'smtpHostType',
+						'value' => SMTPHOSTTYPE,
+						'options' => array(
+							'ssl' => 'ssl',
+							'tls' => 'tls',
+						),
+					),
 					array(
 						array(
 							'type' => 'checkbox',

+ 9 - 3
user.php

@@ -134,7 +134,7 @@
             $mail->SMTPAuth = SMTPHOSTAUTH;
             $mail->Username = SMTPHOSTUSERNAME;
             $mail->Password = SMTPHOSTPASSWORD;
-            $mail->SMTPSecure = 'tls';
+            $mail->SMTPSecure = SMTPHOSTTYPE;
             $mail->Port = SMTPHOSTPORT;
             $mail->setFrom(SMTPHOSTSENDEREMAIL, SMTPHOSTSENDERNAME);
             $mail->addReplyTo(SMTPHOSTSENDEREMAIL, SMTPHOSTSENDERNAME);
@@ -142,7 +142,13 @@
             $mail->addAddress($email, $username);
             $mail->Subject = $subject;
             $mail->Body    = $body;
-            $mail->send();
+            //$mail->send();
+            if(!$mail->send()) {
+                $this->error('Mailer Error: ' . $mail->ErrorInfo);
+                $this->error = 'Mailer Error: ' . $mail->ErrorInfo;
+            } else {
+                $this->info('E-Mail sent!');
+            }
             
         }
        
@@ -352,7 +358,7 @@ EOT;
 			$dbpassword = $this->token_hash_password($username, $sha1, $token);
 			$update = "UPDATE users SET password = '$dbpassword' WHERE email= '$email'";
 			$this->database->exec($update);
-            $this->info("Email has been sent with new password");
+            //$this->info("Email has been sent with new password");
 			// step 3: notify the user of the new password
 			$from = User::MAILER_NAME;
 			$replyto = User::MAILER_REPLYTO;

+ 1 - 1
vendor/phpmailer/phpmailer/class.phpmailer.php

@@ -312,7 +312,7 @@ class PHPMailer
      * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
      * @var integer
      */
-    public $Timeout = 300;
+    public $Timeout = 10;
 
     /**
      * SMTP class debug output mode.