Add password rating

Thanks @Olli :)
diff --git a/templates/register.htm b/templates/register.htm
index edfab84..bfd6318 100644
--- a/templates/register.htm
+++ b/templates/register.htm
@@ -63,20 +63,31 @@
         <input pattern="[^\s]{<?php echo $VAL_USER->min_password.','.$VAL_USER->max_password;?>}" required
             title="At least 8 not whitespace characters" name="password" type="password" id="pw1"
             class="form-control <?php if(isset($error) && $error && !isset($_POST['password'])){echo 'border-danger';}?>"
-            placeholder="********"
+            placeholder="********" oninput="passUpdated()"
             value="<?php echo isset($_POST['password']) ? htmlspecialchars($_POST['password']) : '' ?>" />
+        <div class="progress mt-2">
+            <div class="progress-bar bg-danger" role="progressbar" id="pwqbar" aria-valuenow="0" aria-valuemin="0"
+                aria-valuemax="100"></div>
+        </div>
     </div>
+    <!--
+    <div class="form-group">
+        <label for="pwqinfo">Password rating</label>
+        <input class="form-control" type="text" name="pwqinfo" id="pwqinfo" readonly>
+        <div class="progress mt-2">
+            <div class="progress-bar bg-danger" role="progressbar" id="pwqbar2" aria-valuenow="0" aria-valuemin="0"
+                aria-valuemax="100"></div>
+        </div>
+    </div>
+    -->
     <!-- Password input -->
     <div class="form-outline mb-3">
         <label class="form-label font-weight-bold" for="pw2">Confirm your Password*</label>
         <input pattern="[^\s]{<?php echo $VAL_USER->min_password.','.$VAL_USER->max_password;?>}" required
             title="At least 8 not whitespace characters" name="password_confirm" type="password" id="pw2"
             class="form-control <?php if(isset($error) && $error && !isset($_POST['password_confirm'])){echo 'border-danger';}?>"
-            placeholder="********"
-            oninput="validate_pw2(this)"
-             />
-    </div>
-    <hr class="mt-2 mb-3" />
+            placeholder="********" oninput="validate_pw2(this)" />
+    </div> <hr class="mt-2 mb-3" />
     <div class="form-outline mb-3">
         <label class="form-label font-weight-bold" for="eula">End User License Agreement*</label>
         <div class="form-group form-check">
@@ -159,4 +170,223 @@
                 pw2.setCustomValidity(""); // is valid
             }
         }
+
+        TOO_SHORT      ='Password too short, still %% characters needed';
+        TOO_LONG       ='Password too long, please remove %% characters';
+        INVALID_CHARS  ='Password contains invalid characters';
+        QUAL_NONE      ='Password is very weak'
+        QUAL_LOW       ='Password is weak';
+        QUAL_MEDIUM    ='Password is average'
+        QUAL_GOOD      ='Password is good';
+        QUAL_STRONG    ='Password is strong';
+        REP_OK         ='Repetition ok';
+        REP_NE         ='Passwords not identical';
+        PWNED          ='Password found in public password list';
+
+        String.prototype.strReverse=function() {
+            var newstring='';
+            for (var s=0; s < this.length; s++)
+                newstring=this.charAt(s)+newstring;
+            return newstring;
+        };
+
+        //var checkTimer;
+
+        function passUpdated() {
+            var nScore=0;
+            var message='';
+
+            var pass=$('#pw1').val();
+            var pass2=$('#pw2').val();
+
+            //clearTimeout(checkTimer);
+
+            try {
+                if (!pass)
+                    throw '';
+
+                if (pass.match(/[^a-zA-Z0-9!@#$%()_+=:;",.?/-]/))
+                    throw INVALID_CHARS;
+
+                var nLength=pass.length;
+                if (nLength < 8)
+                    throw TOO_SHORT.replace('%%', 8-nLength);
+                if (nLength > 20)
+                    throw TOO_LONG.replace('%%', nLength-20);
+
+                nScore=4*nLength;
+
+                // check for upper-/lowercase, numeric and special chars pattern matches
+                var nAlphaUC=0, nAlphaLC=0, nNumber=0, nSpecial=0;
+                var nMidChar=0, nRepChar=0, nRepInc=0;
+                var nConsecAlphaUC=0, nConsecAlphaLC=0, nConsecNumber=0;
+                var nTmpAlphaUC='', nTmpAlphaLC='', nTmpNumber='';
+                for (var i=0; i < nLength; i++) {
+                    if (pass[i].match(/[A-Z]/g)) {   // uppercase characters
+                        if (nTmpAlphaUC !== '' && (nTmpAlphaUC+1) == i) {
+                            nConsecAlphaUC++;
+                        }
+                        nTmpAlphaUC=i;
+                        nAlphaUC++;
+                    } else if (pass[i].match(/[a-z]/g)) {   // lowercase characters
+                        if (nTmpAlphaLC !== '' && (nTmpAlphaLC+1) == i) {
+                            nConsecAlphaLC++;
+                        }
+                        nTmpAlphaLC=i;
+                        nAlphaLC++;
+                    } else if (pass[i].match(/[0-9]/g)) {   // numbers
+                        if (i > 0 && i < (nLength-1)) {
+                            nMidChar++;
+                        }
+                        if (nTmpNumber !== '' && (nTmpNumber+1) == i) {
+                            nConsecNumber++;
+                        }
+                        nTmpNumber=i;
+                        nNumber++;
+                    } else {   // special characters
+                        if (i > 0 && i < (nLength-1)) {
+                            nMidChar++;
+                        }
+                        nSpecial++;
+                    }
+
+                    // check for repeated characters
+                    var bCharExists=false;
+                    for (var j=0; j < nLength; j++) {
+                        if (pass[i] == pass[j] && i != j) {
+                            bCharExists=true;
+                            nRepInc+=Math.abs(nLength/(j-i));
+                        }
+                    }
+                    if (bCharExists) {
+                        nRepChar++;
+                        var nUnqChar=nLength-nRepChar;
+                        nRepInc=(nUnqChar) ? Math.ceil(nRepInc/nUnqChar) : Math.ceil(nRepInc);
+                    }
+                }
+
+                // check for sequential alpha string patterns (forward and reverse)
+                var sAlphas="abcdefghijklmnopqrstuvwxyz";
+                var nSeqAlpha=0;
+                for (var i=0; i < 23; i++) {
+                    var sFwd=sAlphas.substring(i, i+3);
+                    var sRev=sFwd.strReverse();
+                    if (pass.toLowerCase().indexOf(sFwd) != -1
+                            ||  pass.toLowerCase().indexOf(sRev) != -1)
+                        nSeqAlpha++;
+                }
+
+                // check for sequential numeric string patterns (forward and reverse)
+                var sNumerics="01234567890";
+                var nSeqNumber=0;
+                for (var i=0; i < 8; i++) {
+                    var sFwd=sNumerics.substring(i, i+3);
+                    var sRev=sFwd.strReverse();
+                    if (pass.toLowerCase().indexOf(sFwd) != -1
+                            ||  pass.toLowerCase().indexOf(sRev) != -1)
+                        nSeqNumber++;
+                }
+
+                // general point assignment
+                if (nAlphaUC > 0 && nAlphaUC < nLength)   // uppercase characters
+                    nScore+=2*(nLength-nAlphaUC);
+                if (nAlphaLC > 0 && nAlphaLC < nLength)   // lowercase characters
+                    nScore+=2*(nLength-nAlphaLC);
+                if (nNumber > 0 && nNumber < nLength)   // numbers
+                    nScore+=2*nNumber;
+                if (nSpecial > 0)   // special characters
+                    nScore+=4*nSpecial;
+                if (nMidChar > 0)   // mid numbers/special characters
+                    nScore+=2*nMidChar;
+
+                // point deductions for poor practices
+                if ((nAlphaLC > 0 || nAlphaUC > 0)
+                    && nSpecial === 0 && nNumber === 0)   // characters only
+                    nScore-=nLength;
+                if (nAlphaLC === 0 && nAlphaUC === 0
+                    && nSpecial === 0 && nNumber > 0)   // numbers only
+                    nScore-=nLength;
+                if (nRepChar > 0)   // same character exists more than once
+                    nScore-=nRepInc;
+                if (nConsecAlphaUC > 0)   // consecutive uppercase letters exist
+                    nScore-=2*nConsecAlphaUC;
+                if (nConsecAlphaLC > 0)   // consecutive lowercase letters exist
+                    nScore-=2*nConsecAlphaLC;
+                if (nConsecNumber > 0)   // consecutive numbers exist
+                    nScore-=2*nConsecNumber;
+                if (nSeqAlpha > 0)   // sequential alpha strings exist (3 chars or more)
+                    nScore-=3*nSeqAlpha;
+                if (nSeqNumber > 0)   // sequential numeric strings exist (3 chars or more)
+                    nScore-=3*nSeqNumber;
+
+                // determine if mandatory requirements have been met
+                var arrChars=[nAlphaUC, nAlphaLC, nNumber, nSpecial];
+                var nReqChar=0;
+                for (var i=0; i < arrChars.length; i++) {
+                    if (arrChars[i]) {
+                        nReqChar++;
+                    }
+                }
+                if (nReqChar >= arrChars.length)
+                    nScore+=2*nReqChar;
+                else if (nReqChar < arrChars.length-1)
+                    nScore-=2*nReqChar;
+
+                // limit points to 3..100
+                nScore=Math.max(3, Math.min(nScore, 100));
+
+                // set message according to points
+                if (nScore >= 80)
+                    message=QUAL_STRONG;
+                else if (nScore >= 60)
+                    message=QUAL_GOOD;
+                else if (nScore >= 40)
+                    message=QUAL_MEDIUM;
+                else if (nScore >= 20)
+                    message=QUAL_LOW;
+                else
+                    message=QUAL_NONE;
+            } catch (error) {
+                nScore=3;
+                message=error;
+            }
+/*
+            if (pass.length > 0 && pass2.length > 0) {
+                message+=' / ';
+                message+=(pass == pass2) ? REP_OK : REP_NE;
+            }
+*/
+            $('#pwqinfo').val(message);
+
+            var progress=$('#pwqbar');
+            progress.width(nScore + '%');
+            progress.attr('aria-valuenow', nScore);
+            if (nScore >= 60)
+                progress.removeClass('bg-danger bg-warning').addClass('bg-success');
+            else if (nScore >= 40)
+                progress.removeClass('bg-danger bg-success').addClass('bg-warning');
+            else
+                progress.removeClass('bg-warning bg-success').addClass('bg-danger');
+
+            if (nScore > 60) {
+                $('#btn_change').prop('disabled', pass !== pass2);
+            } else {
+                $('#btn_change').prop('disabled', 1);
+            }
+/*
+            if (nScore >= 60) {
+                checkTimer=setTimeout(function() {
+                    $.post('checkpass.php', 'pass='+pass, function(ret) {
+                        if (ret !== 'PWNED') {
+                            $('#btn_change').prop('disabled', pass !== pass2);
+                            return;
+                        }
+                        $('#pwqinfo').val(PWNED);
+                        progress.removeClass().addClass('low');
+                        progress.val(3);
+                    });
+                }, 300);
+            }
+*/
+        }
 </script>
\ No newline at end of file