From bade1ad0adc9030d8504ddcd268180a2510ba3fa Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 10 Dec 2016 21:04:17 +0100 Subject: [PATCH 01/58] Add "Settings" to main navigation --- header.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/header.php b/header.php index 6d92a4ad..e93fdde2 100644 --- a/header.php +++ b/header.php @@ -288,6 +288,12 @@ echo '
  • Enable
  • '; } ?> + +
  • + + Settings + +
  • Date: Sat, 10 Dec 2016 21:05:08 +0100 Subject: [PATCH 02/58] Add settings.php --- settings.php | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 settings.php diff --git a/settings.php b/settings.php new file mode 100644 index 00000000..779ab2b6 --- /dev/null +++ b/settings.php @@ -0,0 +1,8 @@ + + + + From ce65caa08f39231d0d937aa7eca437e7296185d1 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 10 Dec 2016 21:10:45 +0100 Subject: [PATCH 03/58] Added jQuery inputmask scripts --- js/other/jquery.inputmask.extensions.js | 122 ++ js/other/jquery.inputmask.js | 1627 +++++++++++++++++++++++ 2 files changed, 1749 insertions(+) create mode 100644 js/other/jquery.inputmask.extensions.js create mode 100644 js/other/jquery.inputmask.js diff --git a/js/other/jquery.inputmask.extensions.js b/js/other/jquery.inputmask.extensions.js new file mode 100644 index 00000000..c89f91ee --- /dev/null +++ b/js/other/jquery.inputmask.extensions.js @@ -0,0 +1,122 @@ +/* +Input Mask plugin extensions +http://github.com/RobinHerbots/jquery.inputmask +Copyright (c) 2010 - 2014 Robin Herbots +Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php) +Version: 0.0.0 + +Optional extensions on the jquery.inputmask base +*/ +(function ($) { + //extra definitions + $.extend($.inputmask.defaults.definitions, { + 'A': { + validator: "[A-Za-z]", + cardinality: 1, + casing: "upper" //auto uppercasing + }, + '#': { + validator: "[A-Za-z\u0410-\u044F\u0401\u04510-9]", + cardinality: 1, + casing: "upper" + } + }); + $.extend($.inputmask.defaults.aliases, { + 'url': { + mask: "ir", + placeholder: "", + separator: "", + defaultPrefix: "http://", + regex: { + urlpre1: new RegExp("[fh]"), + urlpre2: new RegExp("(ft|ht)"), + urlpre3: new RegExp("(ftp|htt)"), + urlpre4: new RegExp("(ftp:|http|ftps)"), + urlpre5: new RegExp("(ftp:/|ftps:|http:|https)"), + urlpre6: new RegExp("(ftp://|ftps:/|http:/|https:)"), + urlpre7: new RegExp("(ftp://|ftps://|http://|https:/)"), + urlpre8: new RegExp("(ftp://|ftps://|http://|https://)") + }, + definitions: { + 'i': { + validator: function (chrs, buffer, pos, strict, opts) { + return true; + }, + cardinality: 8, + prevalidator: (function () { + var result = [], prefixLimit = 8; + for (var i = 0; i < prefixLimit; i++) { + result[i] = (function () { + var j = i; + return { + validator: function (chrs, buffer, pos, strict, opts) { + if (opts.regex["urlpre" + (j + 1)]) { + var tmp = chrs, k; + if (((j + 1) - chrs.length) > 0) { + tmp = buffer.join('').substring(0, ((j + 1) - chrs.length)) + "" + tmp; + } + var isValid = opts.regex["urlpre" + (j + 1)].test(tmp); + if (!strict && !isValid) { + pos = pos - j; + for (k = 0; k < opts.defaultPrefix.length; k++) { + buffer[pos] = opts.defaultPrefix[k]; pos++; + } + for (k = 0; k < tmp.length - 1; k++) { + buffer[pos] = tmp[k]; pos++; + } + return { "pos": pos }; + } + return isValid; + } else { + return false; + } + }, cardinality: j + }; + })(); + } + return result; + })() + }, + "r": { + validator: ".", + cardinality: 50 + } + }, + insertMode: false, + autoUnmask: false + }, + "ip": { //ip-address mask + mask: ["[[x]y]z.[[x]y]z.[[x]y]z.x[yz]", "[[x]y]z.[[x]y]z.[[x]y]z.[[x]y][z]"], + definitions: { + 'x': { + validator: "[012]", + cardinality: 1, + definitionSymbol: "i" + }, + 'y': { + validator: function (chrs, buffer, pos, strict, opts) { + if (pos - 1 > -1 && buffer[pos - 1] != ".") + chrs = buffer[pos - 1] + chrs; + else chrs = "0" + chrs; + return new RegExp("2[0-5]|[01][0-9]").test(chrs); + }, + cardinality: 1, + definitionSymbol: "i" + }, + 'z': { + validator: function (chrs, buffer, pos, strict, opts) { + if (pos - 1 > -1 && buffer[pos - 1] != ".") { + chrs = buffer[pos - 1] + chrs; + if (pos - 2 > -1 && buffer[pos - 2] != ".") { + chrs = buffer[pos - 2] + chrs; + } else chrs = "0" + chrs; + } else chrs = "00" + chrs; + return new RegExp("25[0-5]|2[0-4][0-9]|[01][0-9][0-9]").test(chrs); + }, + cardinality: 1, + definitionSymbol: "i" + } + } + } + }); +})(jQuery); diff --git a/js/other/jquery.inputmask.js b/js/other/jquery.inputmask.js new file mode 100644 index 00000000..86cb3205 --- /dev/null +++ b/js/other/jquery.inputmask.js @@ -0,0 +1,1627 @@ +/** +* @license Input Mask plugin for jquery +* http://github.com/RobinHerbots/jquery.inputmask +* Copyright (c) 2010 - 2014 Robin Herbots +* Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php) +* Version: 0.0.0 +*/ + +(function ($) { + if ($.fn.inputmask === undefined) { + //helper functions + function isInputEventSupported(eventName) { + var el = document.createElement('input'), + eventName = 'on' + eventName, + isSupported = (eventName in el); + if (!isSupported) { + el.setAttribute(eventName, 'return;'); + isSupported = typeof el[eventName] == 'function'; + } + el = null; + return isSupported; + } + function resolveAlias(aliasStr, options, opts) { + var aliasDefinition = opts.aliases[aliasStr]; + if (aliasDefinition) { + if (aliasDefinition.alias) resolveAlias(aliasDefinition.alias, undefined, opts); //alias is another alias + $.extend(true, opts, aliasDefinition); //merge alias definition in the options + $.extend(true, opts, options); //reapply extra given options + return true; + } + return false; + } + function generateMaskSets(opts) { + var ms = []; + var genmasks = []; //used to keep track of the masks that where processed, to avoid duplicates + function getMaskTemplate(mask) { + if (opts.numericInput) { + mask = mask.split('').reverse().join(''); + } + var escaped = false, outCount = 0, greedy = opts.greedy, repeat = opts.repeat; + if (repeat == "*") greedy = false; + //if (greedy == true && opts.placeholder == "") opts.placeholder = " "; + if (mask.length == 1 && greedy == false && repeat != 0) { opts.placeholder = ""; } //hide placeholder with single non-greedy mask + var singleMask = $.map(mask.split(""), function (element, index) { + var outElem = []; + if (element == opts.escapeChar) { + escaped = true; + } + else if ((element != opts.optionalmarker.start && element != opts.optionalmarker.end) || escaped) { + var maskdef = opts.definitions[element]; + if (maskdef && !escaped) { + for (var i = 0; i < maskdef.cardinality; i++) { + outElem.push(opts.placeholder.charAt((outCount + i) % opts.placeholder.length)); + } + } else { + outElem.push(element); + escaped = false; + } + outCount += outElem.length; + return outElem; + } + }); + + //allocate repetitions + var repeatedMask = singleMask.slice(); + for (var i = 1; i < repeat && greedy; i++) { + repeatedMask = repeatedMask.concat(singleMask.slice()); + } + + return { "mask": repeatedMask, "repeat": repeat, "greedy": greedy }; + } + //test definition => {fn: RegExp/function, cardinality: int, optionality: bool, newBlockMarker: bool, offset: int, casing: null/upper/lower, def: definitionSymbol} + function getTestingChain(mask) { + if (opts.numericInput) { + mask = mask.split('').reverse().join(''); + } + var isOptional = false, escaped = false; + var newBlockMarker = false; //indicates wheter the begin/ending of a block should be indicated + + return $.map(mask.split(""), function (element, index) { + var outElem = []; + + if (element == opts.escapeChar) { + escaped = true; + } else if (element == opts.optionalmarker.start && !escaped) { + isOptional = true; + newBlockMarker = true; + } + else if (element == opts.optionalmarker.end && !escaped) { + isOptional = false; + newBlockMarker = true; + } + else { + var maskdef = opts.definitions[element]; + if (maskdef && !escaped) { + var prevalidators = maskdef["prevalidator"], prevalidatorsL = prevalidators ? prevalidators.length : 0; + for (var i = 1; i < maskdef.cardinality; i++) { + var prevalidator = prevalidatorsL >= i ? prevalidators[i - 1] : [], validator = prevalidator["validator"], cardinality = prevalidator["cardinality"]; + outElem.push({ fn: validator ? typeof validator == 'string' ? new RegExp(validator) : new function () { this.test = validator; } : new RegExp("."), cardinality: cardinality ? cardinality : 1, optionality: isOptional, newBlockMarker: isOptional == true ? newBlockMarker : false, offset: 0, casing: maskdef["casing"], def: maskdef["definitionSymbol"] || element }); + if (isOptional == true) //reset newBlockMarker + newBlockMarker = false; + } + outElem.push({ fn: maskdef.validator ? typeof maskdef.validator == 'string' ? new RegExp(maskdef.validator) : new function () { this.test = maskdef.validator; } : new RegExp("."), cardinality: maskdef.cardinality, optionality: isOptional, newBlockMarker: newBlockMarker, offset: 0, casing: maskdef["casing"], def: maskdef["definitionSymbol"] || element }); + } else { + outElem.push({ fn: null, cardinality: 0, optionality: isOptional, newBlockMarker: newBlockMarker, offset: 0, casing: null, def: element }); + escaped = false; + } + //reset newBlockMarker + newBlockMarker = false; + return outElem; + } + }); + } + function markOptional(maskPart) { //needed for the clearOptionalTail functionality + return opts.optionalmarker.start + maskPart + opts.optionalmarker.end; + } + function splitFirstOptionalEndPart(maskPart) { + var optionalStartMarkers = 0, optionalEndMarkers = 0, mpl = maskPart.length; + for (var i = 0; i < mpl; i++) { + if (maskPart.charAt(i) == opts.optionalmarker.start) { + optionalStartMarkers++; + } + if (maskPart.charAt(i) == opts.optionalmarker.end) { + optionalEndMarkers++; + } + if (optionalStartMarkers > 0 && optionalStartMarkers == optionalEndMarkers) + break; + } + var maskParts = [maskPart.substring(0, i)]; + if (i < mpl) { + maskParts.push(maskPart.substring(i + 1, mpl)); + } + return maskParts; + } + function splitFirstOptionalStartPart(maskPart) { + var mpl = maskPart.length; + for (var i = 0; i < mpl; i++) { + if (maskPart.charAt(i) == opts.optionalmarker.start) { + break; + } + } + var maskParts = [maskPart.substring(0, i)]; + if (i < mpl) { + maskParts.push(maskPart.substring(i + 1, mpl)); + } + return maskParts; + } + function generateMask(maskPrefix, maskPart, metadata) { + var maskParts = splitFirstOptionalEndPart(maskPart); + var newMask, maskTemplate; + + var masks = splitFirstOptionalStartPart(maskParts[0]); + if (masks.length > 1) { + newMask = maskPrefix + masks[0] + markOptional(masks[1]) + (maskParts.length > 1 ? maskParts[1] : ""); + if ($.inArray(newMask, genmasks) == -1 && newMask != "") { + genmasks.push(newMask); + maskTemplate = getMaskTemplate(newMask); + ms.push({ + "mask": newMask, + "_buffer": maskTemplate["mask"], + "buffer": maskTemplate["mask"].slice(), + "tests": getTestingChain(newMask), + "lastValidPosition": -1, + "greedy": maskTemplate["greedy"], + "repeat": maskTemplate["repeat"], + "metadata": metadata + }); + } + newMask = maskPrefix + masks[0] + (maskParts.length > 1 ? maskParts[1] : ""); + if ($.inArray(newMask, genmasks) == -1 && newMask != "") { + genmasks.push(newMask); + maskTemplate = getMaskTemplate(newMask); + ms.push({ + "mask": newMask, + "_buffer": maskTemplate["mask"], + "buffer": maskTemplate["mask"].slice(), + "tests": getTestingChain(newMask), + "lastValidPosition": -1, + "greedy": maskTemplate["greedy"], + "repeat": maskTemplate["repeat"], + "metadata": metadata + }); + } + if (splitFirstOptionalStartPart(masks[1]).length > 1) { //optional contains another optional + generateMask(maskPrefix + masks[0], masks[1] + maskParts[1], metadata); + } + if (maskParts.length > 1 && splitFirstOptionalStartPart(maskParts[1]).length > 1) { + generateMask(maskPrefix + masks[0] + markOptional(masks[1]), maskParts[1], metadata); + generateMask(maskPrefix + masks[0], maskParts[1], metadata); + } + } + else { + newMask = maskPrefix + maskParts; + if ($.inArray(newMask, genmasks) == -1 && newMask != "") { + genmasks.push(newMask); + maskTemplate = getMaskTemplate(newMask); + ms.push({ + "mask": newMask, + "_buffer": maskTemplate["mask"], + "buffer": maskTemplate["mask"].slice(), + "tests": getTestingChain(newMask), + "lastValidPosition": -1, + "greedy": maskTemplate["greedy"], + "repeat": maskTemplate["repeat"], + "metadata": metadata + }); + } + } + + } + + if ($.isFunction(opts.mask)) { //allow mask to be a preprocessing fn - should return a valid mask + opts.mask = opts.mask.call(this, opts); + } + if ($.isArray(opts.mask)) { + $.each(opts.mask, function (ndx, msk) { + if (msk["mask"] != undefined) { + generateMask("", msk["mask"].toString(), msk); + } else + generateMask("", msk.toString()); + }); + } else generateMask("", opts.mask.toString()); + + return opts.greedy ? ms : ms.sort(function (a, b) { return a["mask"].length - b["mask"].length; }); + } + + var msie10 = navigator.userAgent.match(new RegExp("msie 10", "i")) !== null, + iphone = navigator.userAgent.match(new RegExp("iphone", "i")) !== null, + android = navigator.userAgent.match(new RegExp("android.*safari.*", "i")) !== null, + androidchrome = navigator.userAgent.match(new RegExp("android.*chrome.*", "i")) !== null, + pasteEvent = isInputEventSupported('paste') ? 'paste' : isInputEventSupported('input') ? 'input' : "propertychange"; + + + //masking scope + //actionObj definition see below + function maskScope(masksets, activeMasksetIndex, opts, actionObj) { + var isRTL = false, + valueOnFocus = getActiveBuffer().join(''), + $el, chromeValueOnInput, + skipKeyPressEvent = false, //Safari 5.1.x - modal dialog fires keypress twice workaround + skipInputEvent = false, //skip when triggered from within inputmask + ignorable = false; + + + //maskset helperfunctions + + function getActiveMaskSet() { + return masksets[activeMasksetIndex]; + } + + function getActiveTests() { + return getActiveMaskSet()['tests']; + } + + function getActiveBufferTemplate() { + return getActiveMaskSet()['_buffer']; + } + + function getActiveBuffer() { + return getActiveMaskSet()['buffer']; + } + + function isValid(pos, c, strict) { //strict true ~ no correction or autofill + strict = strict === true; //always set a value to strict to prevent possible strange behavior in the extensions + + function _isValid(position, activeMaskset, c, strict) { + var testPos = determineTestPosition(position), loopend = c ? 1 : 0, chrs = '', buffer = activeMaskset["buffer"]; + for (var i = activeMaskset['tests'][testPos].cardinality; i > loopend; i--) { + chrs += getBufferElement(buffer, testPos - (i - 1)); + } + + if (c) { + chrs += c; + } + + //return is false or a json object => { pos: ??, c: ??} or true + return activeMaskset['tests'][testPos].fn != null ? + activeMaskset['tests'][testPos].fn.test(chrs, buffer, position, strict, opts) + : (c == getBufferElement(activeMaskset['_buffer'], position, true) || c == opts.skipOptionalPartCharacter) ? + { "refresh": true, c: getBufferElement(activeMaskset['_buffer'], position, true), pos: position } + : false; + } + + function PostProcessResults(maskForwards, results) { + var hasValidActual = false; + $.each(results, function (ndx, rslt) { + hasValidActual = $.inArray(rslt["activeMasksetIndex"], maskForwards) == -1 && rslt["result"] !== false; + if (hasValidActual) return false; + }); + if (hasValidActual) { //strip maskforwards + results = $.map(results, function (rslt, ndx) { + if ($.inArray(rslt["activeMasksetIndex"], maskForwards) == -1) { + return rslt; + } else { + masksets[rslt["activeMasksetIndex"]]["lastValidPosition"] = actualLVP; + } + }); + } else { //keep maskforwards with the least forward + var lowestPos = -1, lowestIndex = -1, rsltValid; + $.each(results, function (ndx, rslt) { + if ($.inArray(rslt["activeMasksetIndex"], maskForwards) != -1 && rslt["result"] !== false & (lowestPos == -1 || lowestPos > rslt["result"]["pos"])) { + lowestPos = rslt["result"]["pos"]; + lowestIndex = rslt["activeMasksetIndex"]; + } + }); + results = $.map(results, function (rslt, ndx) { + if ($.inArray(rslt["activeMasksetIndex"], maskForwards) != -1) { + if (rslt["result"]["pos"] == lowestPos) { + return rslt; + } else if (rslt["result"] !== false) { + for (var i = pos; i < lowestPos; i++) { + rsltValid = _isValid(i, masksets[rslt["activeMasksetIndex"]], masksets[lowestIndex]["buffer"][i], true); + if (rsltValid === false) { + masksets[rslt["activeMasksetIndex"]]["lastValidPosition"] = lowestPos - 1; + break; + } else { + setBufferElement(masksets[rslt["activeMasksetIndex"]]["buffer"], i, masksets[lowestIndex]["buffer"][i], true); + masksets[rslt["activeMasksetIndex"]]["lastValidPosition"] = i; + } + } + //also check check for the lowestpos with the new input + rsltValid = _isValid(lowestPos, masksets[rslt["activeMasksetIndex"]], c, true); + if (rsltValid !== false) { + setBufferElement(masksets[rslt["activeMasksetIndex"]]["buffer"], lowestPos, c, true); + masksets[rslt["activeMasksetIndex"]]["lastValidPosition"] = lowestPos; + } + //console.log("ndx " + rslt["activeMasksetIndex"] + " validate " + masksets[rslt["activeMasksetIndex"]]["buffer"].join('') + " lv " + masksets[rslt["activeMasksetIndex"]]['lastValidPosition']); + return rslt; + } + } + }); + } + return results; + } + + if (strict) { + var result = _isValid(pos, getActiveMaskSet(), c, strict); //only check validity in current mask when validating strict + if (result === true) { + result = { "pos": pos }; //always take a possible corrected maskposition into account + } + return result; + } + + var results = [], result = false, currentActiveMasksetIndex = activeMasksetIndex, + actualBuffer = getActiveBuffer().slice(), actualLVP = getActiveMaskSet()["lastValidPosition"], + actualPrevious = seekPrevious(pos), + maskForwards = []; + $.each(masksets, function (index, value) { + if (typeof (value) == "object") { + activeMasksetIndex = index; + + var maskPos = pos; + var lvp = getActiveMaskSet()['lastValidPosition'], + rsltValid; + if (lvp == actualLVP) { + if ((maskPos - actualLVP) > 1) { + for (var i = lvp == -1 ? 0 : lvp; i < maskPos; i++) { + rsltValid = _isValid(i, getActiveMaskSet(), actualBuffer[i], true); + if (rsltValid === false) { + break; + } else { + setBufferElement(getActiveBuffer(), i, actualBuffer[i], true); + if (rsltValid === true) { + rsltValid = { "pos": i }; //always take a possible corrected maskposition into account + } + var newValidPosition = rsltValid.pos || i; + if (getActiveMaskSet()['lastValidPosition'] < newValidPosition) + getActiveMaskSet()['lastValidPosition'] = newValidPosition; //set new position from isValid + } + } + } + //does the input match on a further position? + if (!isMask(maskPos) && !_isValid(maskPos, getActiveMaskSet(), c, strict)) { + var maxForward = seekNext(maskPos) - maskPos; + for (var fw = 0; fw < maxForward; fw++) { + if (_isValid(++maskPos, getActiveMaskSet(), c, strict) !== false) + break; + } + maskForwards.push(activeMasksetIndex); + //console.log('maskforward ' + activeMasksetIndex + " pos " + pos + " maskPos " + maskPos); + } + } + + if (getActiveMaskSet()['lastValidPosition'] >= actualLVP || activeMasksetIndex == currentActiveMasksetIndex) { + if (maskPos >= 0 && maskPos < getMaskLength()) { + result = _isValid(maskPos, getActiveMaskSet(), c, strict); + if (result !== false) { + if (result === true) { + result = { "pos": maskPos }; //always take a possible corrected maskposition into account + } + var newValidPosition = result.pos || maskPos; + if (getActiveMaskSet()['lastValidPosition'] < newValidPosition) + getActiveMaskSet()['lastValidPosition'] = newValidPosition; //set new position from isValid + } + //console.log("pos " + pos + " ndx " + activeMasksetIndex + " validate " + getActiveBuffer().join('') + " lv " + getActiveMaskSet()['lastValidPosition']); + results.push({ "activeMasksetIndex": index, "result": result }); + } + } + } + }); + activeMasksetIndex = currentActiveMasksetIndex; //reset activeMasksetIndex + + return PostProcessResults(maskForwards, results); //return results of the multiple mask validations + } + + function determineActiveMasksetIndex() { + var currentMasksetIndex = activeMasksetIndex, + highestValid = { "activeMasksetIndex": 0, "lastValidPosition": -1, "next": -1 }; + $.each(masksets, function (index, value) { + if (typeof (value) == "object") { + activeMasksetIndex = index; + if (getActiveMaskSet()['lastValidPosition'] > highestValid['lastValidPosition']) { + highestValid["activeMasksetIndex"] = index; + highestValid["lastValidPosition"] = getActiveMaskSet()['lastValidPosition']; + highestValid["next"] = seekNext(getActiveMaskSet()['lastValidPosition']); + } else if (getActiveMaskSet()['lastValidPosition'] == highestValid['lastValidPosition'] && + (highestValid['next'] == -1 || highestValid['next'] > seekNext(getActiveMaskSet()['lastValidPosition']))) { + highestValid["activeMasksetIndex"] = index; + highestValid["lastValidPosition"] = getActiveMaskSet()['lastValidPosition']; + highestValid["next"] = seekNext(getActiveMaskSet()['lastValidPosition']); + } + } + }); + + activeMasksetIndex = highestValid["lastValidPosition"] != -1 && masksets[currentMasksetIndex]["lastValidPosition"] == highestValid["lastValidPosition"] ? currentMasksetIndex : highestValid["activeMasksetIndex"]; + if (currentMasksetIndex != activeMasksetIndex) { + clearBuffer(getActiveBuffer(), seekNext(highestValid["lastValidPosition"]), getMaskLength()); + getActiveMaskSet()["writeOutBuffer"] = true; + } + $el.data('_inputmask')['activeMasksetIndex'] = activeMasksetIndex; //store the activeMasksetIndex + } + + function isMask(pos) { + var testPos = determineTestPosition(pos); + var test = getActiveTests()[testPos]; + + return test != undefined ? test.fn : false; + } + + function determineTestPosition(pos) { + return pos % getActiveTests().length; + } + + function getMaskLength() { + return opts.getMaskLength(getActiveBufferTemplate(), getActiveMaskSet()['greedy'], getActiveMaskSet()['repeat'], getActiveBuffer(), opts); + } + + //pos: from position + + function seekNext(pos) { + var maskL = getMaskLength(); + if (pos >= maskL) return maskL; + var position = pos; + while (++position < maskL && !isMask(position)) { + } + return position; + } + + //pos: from position + + function seekPrevious(pos) { + var position = pos; + if (position <= 0) return 0; + + while (--position > 0 && !isMask(position)) { + } + return position; + } + + function setBufferElement(buffer, position, element, autoPrepare) { + if (autoPrepare) position = prepareBuffer(buffer, position); + + var test = getActiveTests()[determineTestPosition(position)]; + var elem = element; + if (elem != undefined && test != undefined) { + switch (test.casing) { + case "upper": + elem = element.toUpperCase(); + break; + case "lower": + elem = element.toLowerCase(); + break; + } + } + + buffer[position] = elem; + } + + function getBufferElement(buffer, position, autoPrepare) { + if (autoPrepare) position = prepareBuffer(buffer, position); + return buffer[position]; + } + + //needed to handle the non-greedy mask repetitions + + function prepareBuffer(buffer, position) { + var j; + while (buffer[position] == undefined && buffer.length < getMaskLength()) { + j = 0; + while (getActiveBufferTemplate()[j] !== undefined) { //add a new buffer + buffer.push(getActiveBufferTemplate()[j++]); + } + } + + return position; + } + + function writeBuffer(input, buffer, caretPos) { + input._valueSet(buffer.join('')); + if (caretPos != undefined) { + caret(input, caretPos); + } + } + + function clearBuffer(buffer, start, end, stripNomasks) { + for (var i = start, maskL = getMaskLength() ; i < end && i < maskL; i++) { + if (stripNomasks === true) { + if (!isMask(i)) + setBufferElement(buffer, i, ""); + } else + setBufferElement(buffer, i, getBufferElement(getActiveBufferTemplate().slice(), i, true)); + } + } + + function setReTargetPlaceHolder(buffer, pos) { + var testPos = determineTestPosition(pos); + setBufferElement(buffer, pos, getBufferElement(getActiveBufferTemplate(), testPos)); + } + + function getPlaceHolder(pos) { + return opts.placeholder.charAt(pos % opts.placeholder.length); + } + + function checkVal(input, writeOut, strict, nptvl, intelliCheck) { + var inputValue = nptvl != undefined ? nptvl.slice() : truncateInput(input._valueGet()).split(''); + + $.each(masksets, function (ndx, ms) { + if (typeof (ms) == "object") { + ms["buffer"] = ms["_buffer"].slice(); + ms["lastValidPosition"] = -1; + ms["p"] = -1; + } + }); + if (strict !== true) activeMasksetIndex = 0; + if (writeOut) input._valueSet(""); //initial clear + var ml = getMaskLength(); + $.each(inputValue, function (ndx, charCode) { + if (intelliCheck === true) { + var p = getActiveMaskSet()["p"], lvp = p == -1 ? p : seekPrevious(p), + pos = lvp == -1 ? ndx : seekNext(lvp); + if ($.inArray(charCode, getActiveBufferTemplate().slice(lvp + 1, pos)) == -1) { + keypressEvent.call(input, undefined, true, charCode.charCodeAt(0), writeOut, strict, ndx); + } + } else { + keypressEvent.call(input, undefined, true, charCode.charCodeAt(0), writeOut, strict, ndx); + } + }); + + if (strict === true && getActiveMaskSet()["p"] != -1) { + getActiveMaskSet()["lastValidPosition"] = seekPrevious(getActiveMaskSet()["p"]); + } + } + + function escapeRegex(str) { + return $.inputmask.escapeRegex.call(this, str); + } + + function truncateInput(inputValue) { + return inputValue.replace(new RegExp("(" + escapeRegex(getActiveBufferTemplate().join('')) + ")*$"), ""); + } + + function clearOptionalTail(input) { + var buffer = getActiveBuffer(), tmpBuffer = buffer.slice(), testPos, pos; + for (var pos = tmpBuffer.length - 1; pos >= 0; pos--) { + var testPos = determineTestPosition(pos); + if (getActiveTests()[testPos].optionality) { + if (!isMask(pos) || !isValid(pos, buffer[pos], true)) + tmpBuffer.pop(); + else break; + } else break; + } + writeBuffer(input, tmpBuffer); + } + + function unmaskedvalue($input, skipDatepickerCheck) { + if (getActiveTests() && (skipDatepickerCheck === true || !$input.hasClass('hasDatepicker'))) { + //checkVal(input, false, true); + var umValue = $.map(getActiveBuffer(), function (element, index) { + return isMask(index) && isValid(index, element, true) ? element : null; + }); + var unmaskedValue = (isRTL ? umValue.reverse() : umValue).join(''); + return opts.onUnMask != undefined ? opts.onUnMask.call(this, getActiveBuffer().join(''), unmaskedValue) : unmaskedValue; + } else { + return $input[0]._valueGet(); + } + } + + function TranslatePosition(pos) { + if (isRTL && typeof pos == 'number' && (!opts.greedy || opts.placeholder != "")) { + var bffrLght = getActiveBuffer().length; + pos = bffrLght - pos; + } + return pos; + } + + function caret(input, begin, end) { + var npt = input.jquery && input.length > 0 ? input[0] : input, range; + if (typeof begin == 'number') { + begin = TranslatePosition(begin); + end = TranslatePosition(end); + if (!$(input).is(':visible')) { + return; + } + end = (typeof end == 'number') ? end : begin; + npt.scrollLeft = npt.scrollWidth; + if (opts.insertMode == false && begin == end) end++; //set visualization for insert/overwrite mode + if (npt.setSelectionRange) { + npt.selectionStart = begin; + npt.selectionEnd = android ? begin : end; + + } else if (npt.createTextRange) { + range = npt.createTextRange(); + range.collapse(true); + range.moveEnd('character', end); + range.moveStart('character', begin); + range.select(); + } + } else { + if (!$(input).is(':visible')) { + return { "begin": 0, "end": 0 }; + } + if (npt.setSelectionRange) { + begin = npt.selectionStart; + end = npt.selectionEnd; + } else if (document.selection && document.selection.createRange) { + range = document.selection.createRange(); + begin = 0 - range.duplicate().moveStart('character', -100000); + end = begin + range.text.length; + } + begin = TranslatePosition(begin); + end = TranslatePosition(end); + return { "begin": begin, "end": end }; + } + } + + function isComplete(buffer) { //return true / false / undefined (repeat *) + if (opts.repeat == "*") return undefined; + var complete = false, highestValidPosition = 0, currentActiveMasksetIndex = activeMasksetIndex; + $.each(masksets, function (ndx, ms) { + if (typeof (ms) == "object") { + activeMasksetIndex = ndx; + var aml = seekPrevious(getMaskLength()); + if (ms["lastValidPosition"] >= highestValidPosition && ms["lastValidPosition"] == aml) { + var msComplete = true; + for (var i = 0; i <= aml; i++) { + var mask = isMask(i), testPos = determineTestPosition(i); + if ((mask && (buffer[i] == undefined || buffer[i] == getPlaceHolder(i))) || (!mask && buffer[i] != getActiveBufferTemplate()[testPos])) { + msComplete = false; + break; + } + } + complete = complete || msComplete; + if (complete) //break loop + return false; + } + highestValidPosition = ms["lastValidPosition"]; + } + }); + activeMasksetIndex = currentActiveMasksetIndex; //reset activeMaskset + return complete; + } + + function isSelection(begin, end) { + return isRTL ? (begin - end) > 1 || ((begin - end) == 1 && opts.insertMode) : + (end - begin) > 1 || ((end - begin) == 1 && opts.insertMode); + } + + + //private functions + function installEventRuler(npt) { + var events = $._data(npt).events; + + $.each(events, function (eventType, eventHandlers) { + $.each(eventHandlers, function (ndx, eventHandler) { + if (eventHandler.namespace == "inputmask") { + if (eventHandler.type != "setvalue") { + var handler = eventHandler.handler; + eventHandler.handler = function (e) { + if (this.readOnly || this.disabled) + e.preventDefault; + else + return handler.apply(this, arguments); + }; + } + } + }); + }); + } + + function patchValueProperty(npt) { + var valueProperty; + if (Object.getOwnPropertyDescriptor) + valueProperty = Object.getOwnPropertyDescriptor(npt, "value"); + if (valueProperty && valueProperty.get) { + if (!npt._valueGet) { + var valueGet = valueProperty.get; + var valueSet = valueProperty.set; + npt._valueGet = function () { + return isRTL ? valueGet.call(this).split('').reverse().join('') : valueGet.call(this); + }; + npt._valueSet = function (value) { + valueSet.call(this, isRTL ? value.split('').reverse().join('') : value); + }; + + Object.defineProperty(npt, "value", { + get: function () { + var $self = $(this), inputData = $(this).data('_inputmask'), masksets = inputData['masksets'], + activeMasksetIndex = inputData['activeMasksetIndex']; + return inputData && inputData['opts'].autoUnmask ? $self.inputmask('unmaskedvalue') : valueGet.call(this) != masksets[activeMasksetIndex]['_buffer'].join('') ? valueGet.call(this) : ''; + }, + set: function (value) { + valueSet.call(this, value); + $(this).triggerHandler('setvalue.inputmask'); + } + }); + } + } else if (document.__lookupGetter__ && npt.__lookupGetter__("value")) { + if (!npt._valueGet) { + var valueGet = npt.__lookupGetter__("value"); + var valueSet = npt.__lookupSetter__("value"); + npt._valueGet = function () { + return isRTL ? valueGet.call(this).split('').reverse().join('') : valueGet.call(this); + }; + npt._valueSet = function (value) { + valueSet.call(this, isRTL ? value.split('').reverse().join('') : value); + }; + + npt.__defineGetter__("value", function () { + var $self = $(this), inputData = $(this).data('_inputmask'), masksets = inputData['masksets'], + activeMasksetIndex = inputData['activeMasksetIndex']; + return inputData && inputData['opts'].autoUnmask ? $self.inputmask('unmaskedvalue') : valueGet.call(this) != masksets[activeMasksetIndex]['_buffer'].join('') ? valueGet.call(this) : ''; + }); + npt.__defineSetter__("value", function (value) { + valueSet.call(this, value); + $(this).triggerHandler('setvalue.inputmask'); + }); + } + } else { + if (!npt._valueGet) { + npt._valueGet = function () { return isRTL ? this.value.split('').reverse().join('') : this.value; }; + npt._valueSet = function (value) { this.value = isRTL ? value.split('').reverse().join('') : value; }; + } + if ($.valHooks.text == undefined || $.valHooks.text.inputmaskpatch != true) { + var valueGet = $.valHooks.text && $.valHooks.text.get ? $.valHooks.text.get : function (elem) { return elem.value; }; + var valueSet = $.valHooks.text && $.valHooks.text.set ? $.valHooks.text.set : function (elem, value) { + elem.value = value; + return elem; + }; + + jQuery.extend($.valHooks, { + text: { + get: function (elem) { + var $elem = $(elem); + if ($elem.data('_inputmask')) { + if ($elem.data('_inputmask')['opts'].autoUnmask) + return $elem.inputmask('unmaskedvalue'); + else { + var result = valueGet(elem), + inputData = $elem.data('_inputmask'), masksets = inputData['masksets'], + activeMasksetIndex = inputData['activeMasksetIndex']; + return result != masksets[activeMasksetIndex]['_buffer'].join('') ? result : ''; + } + } else return valueGet(elem); + }, + set: function (elem, value) { + var $elem = $(elem); + var result = valueSet(elem, value); + if ($elem.data('_inputmask')) $elem.triggerHandler('setvalue.inputmask'); + return result; + }, + inputmaskpatch: true + } + }); + } + } + } + + //shift chars to left from start to end and put c at end position if defined + + function shiftL(start, end, c, maskJumps) { + var buffer = getActiveBuffer(); + if (maskJumps !== false) //jumping over nonmask position + while (!isMask(start) && start - 1 >= 0) start--; + for (var i = start; i < end && i < getMaskLength() ; i++) { + if (isMask(i)) { + setReTargetPlaceHolder(buffer, i); + var j = seekNext(i); + var p = getBufferElement(buffer, j); + if (p != getPlaceHolder(j)) { + if (j < getMaskLength() && isValid(i, p, true) !== false && getActiveTests()[determineTestPosition(i)].def == getActiveTests()[determineTestPosition(j)].def) { + setBufferElement(buffer, i, p, true); + } else { + if (isMask(i)) + break; + } + } + } else { + setReTargetPlaceHolder(buffer, i); + } + } + if (c != undefined) + setBufferElement(buffer, seekPrevious(end), c); + + if (getActiveMaskSet()["greedy"] == false) { + var trbuffer = truncateInput(buffer.join('')).split(''); + buffer.length = trbuffer.length; + for (var i = 0, bl = buffer.length; i < bl; i++) { + buffer[i] = trbuffer[i]; + } + if (buffer.length == 0) getActiveMaskSet()["buffer"] = getActiveBufferTemplate().slice(); + } + return start; //return the used start position + } + + function shiftR(start, end, c) { + var buffer = getActiveBuffer(); + if (getBufferElement(buffer, start, true) != getPlaceHolder(start)) { + for (var i = seekPrevious(end) ; i > start && i >= 0; i--) { + if (isMask(i)) { + var j = seekPrevious(i); + var t = getBufferElement(buffer, j); + if (t != getPlaceHolder(j)) { + if (isValid(j, t, true) !== false && getActiveTests()[determineTestPosition(i)].def == getActiveTests()[determineTestPosition(j)].def) { + setBufferElement(buffer, i, t, true); + setReTargetPlaceHolder(buffer, j); + } //else break; + } + } else + setReTargetPlaceHolder(buffer, i); + } + } + if (c != undefined && getBufferElement(buffer, start) == getPlaceHolder(start)) + setBufferElement(buffer, start, c); + var lengthBefore = buffer.length; + if (getActiveMaskSet()["greedy"] == false) { + var trbuffer = truncateInput(buffer.join('')).split(''); + buffer.length = trbuffer.length; + for (var i = 0, bl = buffer.length; i < bl; i++) { + buffer[i] = trbuffer[i]; + } + if (buffer.length == 0) getActiveMaskSet()["buffer"] = getActiveBufferTemplate().slice(); + } + return end - (lengthBefore - buffer.length); //return new start position + } + + function HandleRemove(input, k, pos) { + if (opts.numericInput || isRTL) { + switch (k) { + case opts.keyCode.BACKSPACE: + k = opts.keyCode.DELETE; + break; + case opts.keyCode.DELETE: + k = opts.keyCode.BACKSPACE; + break; + } + if (isRTL) { + var pend = pos.end; + pos.end = pos.begin; + pos.begin = pend; + } + } + + var isSelection = true; + if (pos.begin == pos.end) { + var posBegin = k == opts.keyCode.BACKSPACE ? pos.begin - 1 : pos.begin; + if (opts.isNumeric && opts.radixPoint != "" && getActiveBuffer()[posBegin] == opts.radixPoint) { + pos.begin = (getActiveBuffer().length - 1 == posBegin) /* radixPoint is latest? delete it */ ? pos.begin : k == opts.keyCode.BACKSPACE ? posBegin : seekNext(posBegin); + pos.end = pos.begin; + } + isSelection = false; + if (k == opts.keyCode.BACKSPACE) + pos.begin--; + else if (k == opts.keyCode.DELETE) + pos.end++; + } else if (pos.end - pos.begin == 1 && !opts.insertMode) { + isSelection = false; + if (k == opts.keyCode.BACKSPACE) + pos.begin--; + } + + clearBuffer(getActiveBuffer(), pos.begin, pos.end); + + var ml = getMaskLength(); + if (opts.greedy == false) { + shiftL(pos.begin, ml, undefined, !isRTL && (k == opts.keyCode.BACKSPACE && !isSelection)); + } else { + var newpos = pos.begin; + for (var i = pos.begin; i < pos.end; i++) { //seeknext to skip placeholders at start in selection + if (isMask(i) || !isSelection) + newpos = shiftL(pos.begin, ml, undefined, !isRTL && (k == opts.keyCode.BACKSPACE && !isSelection)); + } + if (!isSelection) pos.begin = newpos; + } + var firstMaskPos = seekNext(-1); + clearBuffer(getActiveBuffer(), pos.begin, pos.end, true); + checkVal(input, false, masksets[1] == undefined || firstMaskPos >= pos.end, getActiveBuffer()); + if (getActiveMaskSet()['lastValidPosition'] < firstMaskPos) { + getActiveMaskSet()["lastValidPosition"] = -1; + getActiveMaskSet()["p"] = firstMaskPos; + } else { + getActiveMaskSet()["p"] = pos.begin; + } + } + + function keydownEvent(e) { + //Safari 5.1.x - modal dialog fires keypress twice workaround + skipKeyPressEvent = false; + var input = this, $input = $(input), k = e.keyCode, pos = caret(input); + + //backspace, delete, and escape get special treatment + if (k == opts.keyCode.BACKSPACE || k == opts.keyCode.DELETE || (iphone && k == 127) || e.ctrlKey && k == 88) { //backspace/delete + e.preventDefault(); //stop default action but allow propagation + if (k == 88) valueOnFocus = getActiveBuffer().join(''); + HandleRemove(input, k, pos); + determineActiveMasksetIndex(); + writeBuffer(input, getActiveBuffer(), getActiveMaskSet()["p"]); + if (input._valueGet() == getActiveBufferTemplate().join('')) + $input.trigger('cleared'); + + if (opts.showTooltip) { //update tooltip + $input.prop("title", getActiveMaskSet()["mask"]); + } + } else if (k == opts.keyCode.END || k == opts.keyCode.PAGE_DOWN) { //when END or PAGE_DOWN pressed set position at lastmatch + setTimeout(function () { + var caretPos = seekNext(getActiveMaskSet()["lastValidPosition"]); + if (!opts.insertMode && caretPos == getMaskLength() && !e.shiftKey) caretPos--; + caret(input, e.shiftKey ? pos.begin : caretPos, caretPos); + }, 0); + } else if ((k == opts.keyCode.HOME && !e.shiftKey) || k == opts.keyCode.PAGE_UP) { //Home or page_up + caret(input, 0, e.shiftKey ? pos.begin : 0); + } else if (k == opts.keyCode.ESCAPE || (k == 90 && e.ctrlKey)) { //escape && undo + checkVal(input, true, false, valueOnFocus.split('')); + $input.click(); + } else if (k == opts.keyCode.INSERT && !(e.shiftKey || e.ctrlKey)) { //insert + opts.insertMode = !opts.insertMode; + caret(input, !opts.insertMode && pos.begin == getMaskLength() ? pos.begin - 1 : pos.begin); + } else if (opts.insertMode == false && !e.shiftKey) { + if (k == opts.keyCode.RIGHT) { + setTimeout(function () { + var caretPos = caret(input); + caret(input, caretPos.begin); + }, 0); + } else if (k == opts.keyCode.LEFT) { + setTimeout(function () { + var caretPos = caret(input); + caret(input, caretPos.begin - 1); + }, 0); + } + } + + var currentCaretPos = caret(input); + if (opts.onKeyDown.call(this, e, getActiveBuffer(), opts) === true) //extra stuff to execute on keydown + caret(input, currentCaretPos.begin, currentCaretPos.end); + ignorable = $.inArray(k, opts.ignorables) != -1; + } + + + function keypressEvent(e, checkval, k, writeOut, strict, ndx) { + //Safari 5.1.x - modal dialog fires keypress twice workaround + if (k == undefined && skipKeyPressEvent) return false; + skipKeyPressEvent = true; + + var input = this, $input = $(input); + + e = e || window.event; + var k = checkval ? k : (e.which || e.charCode || e.keyCode); + + if (checkval !== true && (!(e.ctrlKey && e.altKey) && (e.ctrlKey || e.metaKey || ignorable))) { + return true; + } else { + if (k) { + //special treat the decimal separator + if (checkval !== true && k == 46 && e.shiftKey == false && opts.radixPoint == ",") k = 44; + + var pos, results, result, c = String.fromCharCode(k); + if (checkval) { + var pcaret = strict ? ndx : getActiveMaskSet()["lastValidPosition"] + 1; + pos = { begin: pcaret, end: pcaret }; + } else { + pos = caret(input); + } + + //should we clear a possible selection?? + var isSlctn = isSelection(pos.begin, pos.end), redetermineLVP = false, + initialIndex = activeMasksetIndex; + if (isSlctn) { + activeMasksetIndex = initialIndex; + $.each(masksets, function (ndx, lmnt) { //init undobuffer for recovery when not valid + if (typeof (lmnt) == "object") { + activeMasksetIndex = ndx; + getActiveMaskSet()["undoBuffer"] = getActiveBuffer().join(''); + } + }); + HandleRemove(input, opts.keyCode.DELETE, pos); + if (!opts.insertMode) { //preserve some space + $.each(masksets, function (ndx, lmnt) { + if (typeof (lmnt) == "object") { + activeMasksetIndex = ndx; + shiftR(pos.begin, getMaskLength()); + getActiveMaskSet()["lastValidPosition"] = seekNext(getActiveMaskSet()["lastValidPosition"]); + } + }); + } + activeMasksetIndex = initialIndex; //restore index + } + + var radixPosition = getActiveBuffer().join('').indexOf(opts.radixPoint); + if (opts.isNumeric && checkval !== true && radixPosition != -1) { + if (opts.greedy && pos.begin <= radixPosition) { + pos.begin = seekPrevious(pos.begin); + pos.end = pos.begin; + } else if (c == opts.radixPoint) { + pos.begin = radixPosition; + pos.end = pos.begin; + } + } + + + var p = pos.begin; + results = isValid(p, c, strict); + if (strict === true) results = [{ "activeMasksetIndex": activeMasksetIndex, "result": results }]; + var minimalForwardPosition = -1; + $.each(results, function (index, result) { + activeMasksetIndex = result["activeMasksetIndex"]; + getActiveMaskSet()["writeOutBuffer"] = true; + var np = result["result"]; + if (np !== false) { + var refresh = false, buffer = getActiveBuffer(); + if (np !== true) { + refresh = np["refresh"]; //only rewrite buffer from isValid + p = np.pos != undefined ? np.pos : p; //set new position from isValid + c = np.c != undefined ? np.c : c; //set new char from isValid + } + if (refresh !== true) { + if (opts.insertMode == true) { + var lastUnmaskedPosition = getMaskLength(); + var bfrClone = buffer.slice(); + while (getBufferElement(bfrClone, lastUnmaskedPosition, true) != getPlaceHolder(lastUnmaskedPosition) && lastUnmaskedPosition >= p) { + lastUnmaskedPosition = lastUnmaskedPosition == 0 ? -1 : seekPrevious(lastUnmaskedPosition); + } + if (lastUnmaskedPosition >= p) { + shiftR(p, getMaskLength(), c); + //shift the lvp if needed + var lvp = getActiveMaskSet()["lastValidPosition"], nlvp = seekNext(lvp); + if (nlvp != getMaskLength() && lvp >= p && (getBufferElement(getActiveBuffer(), nlvp, true) != getPlaceHolder(nlvp))) { + getActiveMaskSet()["lastValidPosition"] = nlvp; + } + } else getActiveMaskSet()["writeOutBuffer"] = false; + } else setBufferElement(buffer, p, c, true); + if (minimalForwardPosition == -1 || minimalForwardPosition > seekNext(p)) { + minimalForwardPosition = seekNext(p); + } + } else if (!strict) { + var nextPos = p < getMaskLength() ? p + 1 : p; + if (minimalForwardPosition == -1 || minimalForwardPosition > nextPos) { + minimalForwardPosition = nextPos; + } + } + if (minimalForwardPosition > getActiveMaskSet()["p"]) + getActiveMaskSet()["p"] = minimalForwardPosition; //needed for checkval strict + } + }); + + if (strict !== true) { + activeMasksetIndex = initialIndex; + determineActiveMasksetIndex(); + } + if (writeOut !== false) { + $.each(results, function (ndx, rslt) { + if (rslt["activeMasksetIndex"] == activeMasksetIndex) { + result = rslt; + return false; + } + }); + if (result != undefined) { + var self = this; + setTimeout(function () { opts.onKeyValidation.call(self, result["result"], opts); }, 0); + if (getActiveMaskSet()["writeOutBuffer"] && result["result"] !== false) { + var buffer = getActiveBuffer(); + + var newCaretPosition; + if (checkval) { + newCaretPosition = undefined; + } else if (opts.numericInput) { + if (p > radixPosition) { + newCaretPosition = seekPrevious(minimalForwardPosition); + } else if (c == opts.radixPoint) { + newCaretPosition = minimalForwardPosition - 1; + } else newCaretPosition = seekPrevious(minimalForwardPosition - 1); + } else { + newCaretPosition = minimalForwardPosition; + } + + writeBuffer(input, buffer, newCaretPosition); + if (checkval !== true) { + setTimeout(function () { //timeout needed for IE + if (isComplete(buffer) === true) + $input.trigger("complete"); + skipInputEvent = true; + $input.trigger("input"); + }, 0); + } + } else if (isSlctn) { + getActiveMaskSet()["buffer"] = getActiveMaskSet()["undoBuffer"].split(''); + } + } + } + + if (opts.showTooltip) { //update tooltip + $input.prop("title", getActiveMaskSet()["mask"]); + } + + //needed for IE8 and below + if (e) e.preventDefault ? e.preventDefault() : e.returnValue = false; + } + } + } + + function keyupEvent(e) { + var $input = $(this), input = this, k = e.keyCode, buffer = getActiveBuffer(); + + if (androidchrome && k == opts.keyCode.BACKSPACE) { + if (chromeValueOnInput == input._valueGet()) + keydownEvent.call(this, e); + } + + opts.onKeyUp.call(this, e, buffer, opts); //extra stuff to execute on keyup + if (k == opts.keyCode.TAB && opts.showMaskOnFocus) { + if ($input.hasClass('focus.inputmask') && input._valueGet().length == 0) { + buffer = getActiveBufferTemplate().slice(); + writeBuffer(input, buffer); + caret(input, 0); + valueOnFocus = getActiveBuffer().join(''); + } else { + writeBuffer(input, buffer); + if (buffer.join('') == getActiveBufferTemplate().join('') && $.inArray(opts.radixPoint, buffer) != -1) { + caret(input, TranslatePosition(0)); + $input.click(); + } else + caret(input, TranslatePosition(0), TranslatePosition(getMaskLength())); + } + } + } + + function inputEvent(e) { + if (skipInputEvent === true) { + skipInputEvent = false; + return true; + } + var input = this, $input = $(input); + + chromeValueOnInput = getActiveBuffer().join(''); + checkVal(input, false, false); + writeBuffer(input, getActiveBuffer()); + if (isComplete(getActiveBuffer()) === true) + $input.trigger("complete"); + $input.click(); + } + + function mask(el) { + $el = $(el); + if ($el.is(":input")) { + //store tests & original buffer in the input element - used to get the unmasked value + $el.data('_inputmask', { + 'masksets': masksets, + 'activeMasksetIndex': activeMasksetIndex, + 'opts': opts, + 'isRTL': false + }); + + //show tooltip + if (opts.showTooltip) { + $el.prop("title", getActiveMaskSet()["mask"]); + } + + //correct greedy setting if needed + getActiveMaskSet()['greedy'] = getActiveMaskSet()['greedy'] ? getActiveMaskSet()['greedy'] : getActiveMaskSet()['repeat'] == 0; + + //handle maxlength attribute + if ($el.attr("maxLength") != null) //only when the attribute is set + { + var maxLength = $el.prop('maxLength'); + if (maxLength > -1) { //handle *-repeat + $.each(masksets, function (ndx, ms) { + if (typeof (ms) == "object") { + if (ms["repeat"] == "*") { + ms["repeat"] = maxLength; + } + } + }); + } + if (getMaskLength() >= maxLength && maxLength > -1) { //FF sets no defined max length to -1 + if (maxLength < getActiveBufferTemplate().length) getActiveBufferTemplate().length = maxLength; + if (getActiveMaskSet()['greedy'] == false) { + getActiveMaskSet()['repeat'] = Math.round(maxLength / getActiveBufferTemplate().length); + } + $el.prop('maxLength', getMaskLength() * 2); + } + } + + patchValueProperty(el); + + if (opts.numericInput) opts.isNumeric = opts.numericInput; + if (el.dir == "rtl" || (opts.numericInput && opts.rightAlignNumerics) || (opts.isNumeric && opts.rightAlignNumerics)) + $el.css("text-align", "right"); + + if (el.dir == "rtl" || opts.numericInput) { + el.dir = "ltr"; + $el.removeAttr("dir"); + var inputData = $el.data('_inputmask'); + inputData['isRTL'] = true; + $el.data('_inputmask', inputData); + isRTL = true; + } + + //unbind all events - to make sure that no other mask will interfere when re-masking + $el.unbind(".inputmask"); + $el.removeClass('focus.inputmask'); + //bind events + $el.closest('form').bind("submit", function () { //trigger change on submit if any + if (valueOnFocus != getActiveBuffer().join('')) { + $el.change(); + } + }).bind('reset', function () { + setTimeout(function () { + $el.trigger("setvalue"); + }, 0); + }); + $el.bind("mouseenter.inputmask", function () { + var $input = $(this), input = this; + if (!$input.hasClass('focus.inputmask') && opts.showMaskOnHover) { + if (input._valueGet() != getActiveBuffer().join('')) { + writeBuffer(input, getActiveBuffer()); + } + } + }).bind("blur.inputmask", function () { + var $input = $(this), input = this, nptValue = input._valueGet(), buffer = getActiveBuffer(); + $input.removeClass('focus.inputmask'); + if (valueOnFocus != getActiveBuffer().join('')) { + $input.change(); + } + if (opts.clearMaskOnLostFocus && nptValue != '') { + if (nptValue == getActiveBufferTemplate().join('')) + input._valueSet(''); + else { //clearout optional tail of the mask + clearOptionalTail(input); + } + } + if (isComplete(buffer) === false) { + $input.trigger("incomplete"); + if (opts.clearIncomplete) { + $.each(masksets, function (ndx, ms) { + if (typeof (ms) == "object") { + ms["buffer"] = ms["_buffer"].slice(); + ms["lastValidPosition"] = -1; + } + }); + activeMasksetIndex = 0; + if (opts.clearMaskOnLostFocus) + input._valueSet(''); + else { + buffer = getActiveBufferTemplate().slice(); + writeBuffer(input, buffer); + } + } + } + }).bind("focus.inputmask", function () { + var $input = $(this), input = this, nptValue = input._valueGet(); + if (opts.showMaskOnFocus && !$input.hasClass('focus.inputmask') && (!opts.showMaskOnHover || (opts.showMaskOnHover && nptValue == ''))) { + if (input._valueGet() != getActiveBuffer().join('')) { + writeBuffer(input, getActiveBuffer(), seekNext(getActiveMaskSet()["lastValidPosition"])); + } + } + $input.addClass('focus.inputmask'); + valueOnFocus = getActiveBuffer().join(''); + }).bind("mouseleave.inputmask", function () { + var $input = $(this), input = this; + if (opts.clearMaskOnLostFocus) { + if (!$input.hasClass('focus.inputmask') && input._valueGet() != $input.attr("placeholder")) { + if (input._valueGet() == getActiveBufferTemplate().join('') || input._valueGet() == '') + input._valueSet(''); + else { //clearout optional tail of the mask + clearOptionalTail(input); + } + } + } + }).bind("click.inputmask", function () { + var input = this; + setTimeout(function () { + var selectedCaret = caret(input), buffer = getActiveBuffer(); + if (selectedCaret.begin == selectedCaret.end) { + var clickPosition = isRTL ? TranslatePosition(selectedCaret.begin) : selectedCaret.begin, + lvp = getActiveMaskSet()["lastValidPosition"], + lastPosition; + if (opts.isNumeric) { + lastPosition = opts.skipRadixDance === false && opts.radixPoint != "" && $.inArray(opts.radixPoint, buffer) != -1 ? + (opts.numericInput ? seekNext($.inArray(opts.radixPoint, buffer)) : $.inArray(opts.radixPoint, buffer)) : + seekNext(lvp); + } else { + lastPosition = seekNext(lvp); + } + if (clickPosition < lastPosition) { + if (isMask(clickPosition)) + caret(input, clickPosition); + else caret(input, seekNext(clickPosition)); + } else + caret(input, lastPosition); + } + }, 0); + }).bind('dblclick.inputmask', function () { + var input = this; + setTimeout(function () { + caret(input, 0, seekNext(getActiveMaskSet()["lastValidPosition"])); + }, 0); + }).bind(pasteEvent + ".inputmask dragdrop.inputmask drop.inputmask", function (e) { + if (skipInputEvent === true) { + skipInputEvent = false; + return true; + } + var input = this, $input = $(input); + + //paste event for IE8 and lower I guess ;-) + if (e.type == "propertychange" && input._valueGet().length <= getMaskLength()) { + return true; + } + setTimeout(function () { + var pasteValue = opts.onBeforePaste != undefined ? opts.onBeforePaste.call(this, input._valueGet()) : input._valueGet(); + checkVal(input, true, false, pasteValue.split(''), true); + if (isComplete(getActiveBuffer()) === true) + $input.trigger("complete"); + $input.click(); + }, 0); + }).bind('setvalue.inputmask', function () { + var input = this; + checkVal(input, true); + valueOnFocus = getActiveBuffer().join(''); + if (input._valueGet() == getActiveBufferTemplate().join('')) + input._valueSet(''); + }).bind('complete.inputmask', opts.oncomplete + ).bind('incomplete.inputmask', opts.onincomplete + ).bind('cleared.inputmask', opts.oncleared + ).bind("keyup.inputmask", keyupEvent); + + if (androidchrome) { + $el.bind("input.inputmask", inputEvent); + } else { + $el.bind("keydown.inputmask", keydownEvent + ).bind("keypress.inputmask", keypressEvent); + } + + if (msie10) + $el.bind("input.inputmask", inputEvent); + + //apply mask + checkVal(el, true, false); + valueOnFocus = getActiveBuffer().join(''); + // Wrap document.activeElement in a try/catch block since IE9 throw "Unspecified error" if document.activeElement is undefined when we are in an IFrame. + var activeElement; + try { + activeElement = document.activeElement; + } catch (e) { + } + if (activeElement === el) { //position the caret when in focus + $el.addClass('focus.inputmask'); + caret(el, seekNext(getActiveMaskSet()["lastValidPosition"])); + } else if (opts.clearMaskOnLostFocus) { + if (getActiveBuffer().join('') == getActiveBufferTemplate().join('')) { + el._valueSet(''); + } else { + clearOptionalTail(el); + } + } else { + writeBuffer(el, getActiveBuffer()); + } + + installEventRuler(el); + } + } + + //action object + if (actionObj != undefined) { + switch (actionObj["action"]) { + case "isComplete": + return isComplete(actionObj["buffer"]); + case "unmaskedvalue": + isRTL = actionObj["$input"].data('_inputmask')['isRTL']; + return unmaskedvalue(actionObj["$input"], actionObj["skipDatepickerCheck"]); + case "mask": + mask(actionObj["el"]); + break; + case "format": + $el = $({}); + $el.data('_inputmask', { + 'masksets': masksets, + 'activeMasksetIndex': activeMasksetIndex, + 'opts': opts, + 'isRTL': opts.numericInput + }); + if (opts.numericInput) { + opts.isNumeric = opts.numericInput; + isRTL = true; + } + + checkVal($el, false, false, actionObj["value"].split(''), true); + return getActiveBuffer().join(''); + } + } + } + $.inputmask = { + //options default + defaults: { + placeholder: "_", + optionalmarker: { start: "[", end: "]" }, + quantifiermarker: { start: "{", end: "}" }, + groupmarker: { start: "(", end: ")" }, + escapeChar: "\\", + mask: null, + oncomplete: $.noop, //executes when the mask is complete + onincomplete: $.noop, //executes when the mask is incomplete and focus is lost + oncleared: $.noop, //executes when the mask is cleared + repeat: 0, //repetitions of the mask: * ~ forever, otherwise specify an integer + greedy: true, //true: allocated buffer for the mask and repetitions - false: allocate only if needed + autoUnmask: false, //automatically unmask when retrieving the value with $.fn.val or value if the browser supports __lookupGetter__ or getOwnPropertyDescriptor + clearMaskOnLostFocus: true, + insertMode: true, //insert the input or overwrite the input + clearIncomplete: false, //clear the incomplete input on blur + aliases: {}, //aliases definitions => see jquery.inputmask.extensions.js + onKeyUp: $.noop, //override to implement autocomplete on certain keys for example + onKeyDown: $.noop, //override to implement autocomplete on certain keys for example + onBeforePaste: undefined, //executes before masking the pasted value to allow preprocessing of the pasted value. args => pastedValue => return processedValue + onUnMask: undefined, //executes after unmasking to allow postprocessing of the unmaskedvalue. args => maskedValue, unmaskedValue + showMaskOnFocus: true, //show the mask-placeholder when the input has focus + showMaskOnHover: true, //show the mask-placeholder when hovering the empty input + onKeyValidation: $.noop, //executes on every key-press with the result of isValid. Params: result, opts + skipOptionalPartCharacter: " ", //a character which can be used to skip an optional part of a mask + showTooltip: false, //show the activemask as tooltip + numericInput: false, //numericInput input direction style (input shifts to the left while holding the caret position) + //numeric basic properties + isNumeric: false, //enable numeric features + radixPoint: "", //".", // | "," + skipRadixDance: false, //disable radixpoint caret positioning + rightAlignNumerics: true, //align numerics to the right + //numeric basic properties + definitions: { + '9': { + validator: "[0-9]", + cardinality: 1 + }, + 'a': { + validator: "[A-Za-z\u0410-\u044F\u0401\u0451]", + cardinality: 1 + }, + '*': { + validator: "[A-Za-z\u0410-\u044F\u0401\u04510-9]", + cardinality: 1 + } + }, + keyCode: { + ALT: 18, BACKSPACE: 8, CAPS_LOCK: 20, COMMA: 188, COMMAND: 91, COMMAND_LEFT: 91, COMMAND_RIGHT: 93, CONTROL: 17, DELETE: 46, DOWN: 40, END: 35, ENTER: 13, ESCAPE: 27, HOME: 36, INSERT: 45, LEFT: 37, MENU: 93, NUMPAD_ADD: 107, NUMPAD_DECIMAL: 110, NUMPAD_DIVIDE: 111, NUMPAD_ENTER: 108, + NUMPAD_MULTIPLY: 106, NUMPAD_SUBTRACT: 109, PAGE_DOWN: 34, PAGE_UP: 33, PERIOD: 190, RIGHT: 39, SHIFT: 16, SPACE: 32, TAB: 9, UP: 38, WINDOWS: 91 + }, + //specify keycodes which should not be considered in the keypress event, otherwise the preventDefault will stop their default behavior especially in FF + ignorables: [8, 9, 13, 19, 27, 33, 34, 35, 36, 37, 38, 39, 40, 45, 46, 93, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123], + getMaskLength: function (buffer, greedy, repeat, currentBuffer, opts) { + var calculatedLength = buffer.length; + if (!greedy) { + if (repeat == "*") { + calculatedLength = currentBuffer.length + 1; + } else if (repeat > 1) { + calculatedLength += (buffer.length * (repeat - 1)); + } + } + return calculatedLength; + } + }, + escapeRegex: function (str) { + var specials = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\']; + return str.replace(new RegExp('(\\' + specials.join('|\\') + ')', 'gim'), '\\$1'); + }, + format: function (value, options) { + var opts = $.extend(true, {}, $.inputmask.defaults, options); + resolveAlias(opts.alias, options, opts); + return maskScope(generateMaskSets(opts), 0, opts, { "action": "format", "value": value }); + } + }; + + $.fn.inputmask = function (fn, options) { + var opts = $.extend(true, {}, $.inputmask.defaults, options), + masksets, + activeMasksetIndex = 0; + + if (typeof fn === "string") { + switch (fn) { + case "mask": + //resolve possible aliases given by options + resolveAlias(opts.alias, options, opts); + masksets = generateMaskSets(opts); + if (masksets.length == 0) { return this; } + + return this.each(function () { + maskScope($.extend(true, {}, masksets), 0, opts, { "action": "mask", "el": this }); + }); + case "unmaskedvalue": + var $input = $(this), input = this; + if ($input.data('_inputmask')) { + masksets = $input.data('_inputmask')['masksets']; + activeMasksetIndex = $input.data('_inputmask')['activeMasksetIndex']; + opts = $input.data('_inputmask')['opts']; + return maskScope(masksets, activeMasksetIndex, opts, { "action": "unmaskedvalue", "$input": $input }); + } else return $input.val(); + case "remove": + return this.each(function () { + var $input = $(this), input = this; + if ($input.data('_inputmask')) { + masksets = $input.data('_inputmask')['masksets']; + activeMasksetIndex = $input.data('_inputmask')['activeMasksetIndex']; + opts = $input.data('_inputmask')['opts']; + //writeout the unmaskedvalue + input._valueSet(maskScope(masksets, activeMasksetIndex, opts, { "action": "unmaskedvalue", "$input": $input, "skipDatepickerCheck": true })); + //clear data + $input.removeData('_inputmask'); + //unbind all events + $input.unbind(".inputmask"); + $input.removeClass('focus.inputmask'); + //restore the value property + var valueProperty; + if (Object.getOwnPropertyDescriptor) + valueProperty = Object.getOwnPropertyDescriptor(input, "value"); + if (valueProperty && valueProperty.get) { + if (input._valueGet) { + Object.defineProperty(input, "value", { + get: input._valueGet, + set: input._valueSet + }); + } + } else if (document.__lookupGetter__ && input.__lookupGetter__("value")) { + if (input._valueGet) { + input.__defineGetter__("value", input._valueGet); + input.__defineSetter__("value", input._valueSet); + } + } + try { //try catch needed for IE7 as it does not supports deleting fns + delete input._valueGet; + delete input._valueSet; + } catch (e) { + input._valueGet = undefined; + input._valueSet = undefined; + + } + } + }); + break; + case "getemptymask": //return the default (empty) mask value, usefull for setting the default value in validation + if (this.data('_inputmask')) { + masksets = this.data('_inputmask')['masksets']; + activeMasksetIndex = this.data('_inputmask')['activeMasksetIndex']; + return masksets[activeMasksetIndex]['_buffer'].join(''); + } + else return ""; + case "hasMaskedValue": //check wheter the returned value is masked or not; currently only works reliable when using jquery.val fn to retrieve the value + return this.data('_inputmask') ? !this.data('_inputmask')['opts'].autoUnmask : false; + case "isComplete": + masksets = this.data('_inputmask')['masksets']; + activeMasksetIndex = this.data('_inputmask')['activeMasksetIndex']; + opts = this.data('_inputmask')['opts']; + return maskScope(masksets, activeMasksetIndex, opts, { "action": "isComplete", "buffer": this[0]._valueGet().split('') }); + case "getmetadata": //return mask metadata if exists + if (this.data('_inputmask')) { + masksets = this.data('_inputmask')['masksets']; + activeMasksetIndex = this.data('_inputmask')['activeMasksetIndex']; + return masksets[activeMasksetIndex]['metadata']; + } + else return undefined; + default: + //check if the fn is an alias + if (!resolveAlias(fn, options, opts)) { + //maybe fn is a mask so we try + //set mask + opts.mask = fn; + } + masksets = generateMaskSets(opts); + if (masksets.length == 0) { return this; } + return this.each(function () { + maskScope($.extend(true, {}, masksets), activeMasksetIndex, opts, { "action": "mask", "el": this }); + }); + + break; + } + } else if (typeof fn == "object") { + opts = $.extend(true, {}, $.inputmask.defaults, fn); + + resolveAlias(opts.alias, fn, opts); //resolve aliases + masksets = generateMaskSets(opts); + if (masksets.length == 0) { return this; } + return this.each(function () { + maskScope($.extend(true, {}, masksets), activeMasksetIndex, opts, { "action": "mask", "el": this }); + }); + } else if (fn == undefined) { + //look for data-inputmask atribute - the attribute should only contain optipns + return this.each(function () { + var attrOptions = $(this).attr("data-inputmask"); + if (attrOptions && attrOptions != "") { + try { + attrOptions = attrOptions.replace(new RegExp("'", "g"), '"'); + var dataoptions = $.parseJSON("{" + attrOptions + "}"); + $.extend(true, dataoptions, options); + opts = $.extend(true, {}, $.inputmask.defaults, dataoptions); + resolveAlias(opts.alias, dataoptions, opts); + opts.alias = undefined; + $(this).inputmask(opts); + } catch (ex) { } //need a more relax parseJSON + } + }); + } + }; + } +})(jQuery); From be27a0daa79f1977db6635a359ec8bd4a94f78bb Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 10 Dec 2016 21:25:58 +0100 Subject: [PATCH 04/58] Add pihole/settings.js --- js/pihole/settings.js | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 js/pihole/settings.js diff --git a/js/pihole/settings.js b/js/pihole/settings.js new file mode 100644 index 00000000..5160e9b3 --- /dev/null +++ b/js/pihole/settings.js @@ -0,0 +1,3 @@ +$(function () { + $("[data-mask]").inputmask(); +}); From 1b6151b2cad9532c6f9fe520a4a635c3840750af Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 10 Dec 2016 22:07:06 +0100 Subject: [PATCH 05/58] First static version --- settings.php | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/settings.php b/settings.php index 779ab2b6..3a8b1bda 100644 --- a/settings.php +++ b/settings.php @@ -1,8 +1,149 @@ +
    +
    +
    +
    +

    Networking

    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    Upstream DNS Servers

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +

    Query Logging

    +
    +
    +
    +
    +
    + +
    +
    + Note that disabling will render graphs on the web user interface useless +
    +
    +
    +
    +
    +
    +
    +

    Web User Interface

    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    + + + + From 55c42468e05416fd40ecfb10b989736b94d69cd8 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 11 Dec 2016 16:38:17 +0100 Subject: [PATCH 06/58] Added dynamics --- settings.php | 229 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 170 insertions(+), 59 deletions(-) diff --git a/settings.php b/settings.php index 3a8b1bda..6ebf0690 100644 --- a/settings.php +++ b/settings.php @@ -3,39 +3,105 @@ ?>
    +

    Networking

    -
    - +
    - +
    - + +
    +
    +
    + +
    +
    +
    -
    + "Google", + "208.67.222.222" => "OpenDNS", + "4.2.2.1" => "Level3", + "199.85.126.10" => "Norton", + "8.26.56.26" => "Comodo" + ]; + + $secondaryDNSservers = [ + "8.8.4.4" => "Google", + "208.67.220.220" => "OpenDNS", + "4.2.2.2" => "Level3", + "199.85.127.10" => "Norton", + "8.20.247.20" => "Comodo" + ]; + + if(isset($setupVars["PIHOLE_DNS_1"])){ + if(isset($primaryDNSservers[$setupVars["PIHOLE_DNS_1"]])) + { + $piHoleDNS1 = $primaryDNSservers[$setupVars["PIHOLE_DNS_1"]]; + } + else + { + $piHoleDNS1 = "Custom"; + } + } else { + $piHoleDNS1 = "unknown"; + } + + if(isset($setupVars["PIHOLE_DNS_2"])){ + if(isset($secondaryDNSservers[$setupVars["PIHOLE_DNS_2"]])) + { + $piHoleDNS2 = $secondaryDNSservers[$setupVars["PIHOLE_DNS_2"]]; + } + else + { + $piHoleDNS2 = "Custom"; + } + } else { + $piHoleDNS2 = "unknown"; + } +?>

    Upstream DNS Servers

    @@ -45,14 +111,15 @@
    -
    -
    -
    -
    -
    +
    +
    +
    +
    +
    -
    +
    checked>
    @@ -60,21 +127,39 @@
    -
    -
    -
    -
    -
    +
    +
    +
    +
    +
    -
    - +
    checked>
    + value="">
    +

    Query Logging

    @@ -82,59 +167,85 @@
    -
    - -
    +
    Note that disabling will render graphs on the web user interface useless
    +

    Web User Interface

    +
    - - -
    -
    - -
    -
    - -
    -
    -
    -
    -
    - -
    -
    - -
    +

    Top Lists

    +

    Exclude the following domains from being shown in

    +
    +
    + +
    - +
    +
    + + +
    +
    +

    Query Log Page

    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    + +
    +
    +
    +

    Blocking Page

    +
    +
    +

    Show page with details if a site is blocked

    +
    +
    +
    +

    If Yes: Hide content for in page ads?

    +
    +
    From 3fa48d67a6b8d876d156f19882c64e0f783a040b Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 11 Dec 2016 17:46:40 +0100 Subject: [PATCH 07/58] Disabled some elements added first "Save" button --- settings.php | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/settings.php b/settings.php index 6ebf0690..06c9acca 100644 --- a/settings.php +++ b/settings.php @@ -22,7 +22,7 @@ } $hostname = trim(file_get_contents("/etc/hostname"), "\x00..\x1F"); ?> -
    +

    Networking

    @@ -141,6 +141,9 @@
    +
    @@ -203,26 +206,26 @@
    - +
    - +

    Query Log Page

    -
    -
    +
    +
    - @@ -231,23 +234,38 @@
    +

    CPU Temperature Unit

    +
    +
    +
    +
    -
    +

    Blocking Page

    Show page with details if a site is blocked

    -
    -
    +
    +

    If Yes: Hide content for in page ads?

    -
    +
    +
    +
    +

    System Administration

    +
    +
    + + + +
    +
    From 8c72e84f93a6e27be13aaabd1c9405b359f6fe7d Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 11 Dec 2016 17:51:19 +0100 Subject: [PATCH 08/58] Minor correction + added another button --- settings.php | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/settings.php b/settings.php index 06c9acca..d108ca13 100644 --- a/settings.php +++ b/settings.php @@ -1,5 +1,8 @@
    @@ -141,11 +144,11 @@
    - - + +
    -

    Web User Interface

    +

    API

    @@ -215,6 +218,18 @@
    + + + + +
    +
    +

    Web User Interface

    +
    +
    +

    Query Log Page

    From 1b7122ea1cc918232a7c039c26ca21b393c74663 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 11 Dec 2016 17:52:35 +0100 Subject: [PATCH 09/58] Use POST --- settings.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/settings.php b/settings.php index d108ca13..02e12f6a 100644 --- a/settings.php +++ b/settings.php @@ -110,7 +110,7 @@

    Upstream DNS Servers

    - +
    @@ -171,7 +171,7 @@

    Query Logging

    - +
    @@ -202,7 +202,7 @@

    API

    - +

    Top Lists

    Exclude the following domains from being shown in

    @@ -228,7 +228,7 @@

    Web User Interface

    - +

    Query Log Page

    From f2974d4b61acabe236b1f1ef5b34639f8367ecd3 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 11 Dec 2016 18:14:23 +0100 Subject: [PATCH 10/58] Implemented setting of DNS server IPs --- php/savesettings.php | 75 ++++++++++++++++++++++++++++++++++++++++++++ settings.php | 31 +++++++----------- 2 files changed, 86 insertions(+), 20 deletions(-) create mode 100644 php/savesettings.php diff --git a/php/savesettings.php b/php/savesettings.php new file mode 100644 index 00000000..157ecd5e --- /dev/null +++ b/php/savesettings.php @@ -0,0 +1,75 @@ + "Google", + "208.67.222.222" => "OpenDNS", + "4.2.2.1" => "Level3", + "199.85.126.10" => "Norton", + "8.26.56.26" => "Comodo" + ]; + + $secondaryDNSservers = [ + "8.8.4.4" => "Google", + "208.67.220.220" => "OpenDNS", + "4.2.2.2" => "Level3", + "199.85.127.10" => "Norton", + "8.20.247.20" => "Comodo" + ]; + + if(isset($_POST["field"])) + { + // Process request + switch ($_POST["field"]) { + // Set DNS server + case "DNS": + $primaryDNS = $_POST["primaryDNS"]; + $secondaryDNS = $_POST["secondaryDNS"]; + + // Get primary DNS server IP address + if($primaryDNS === "Custom") + { + $primaryIP = $_POST["DNS1IP"]; + } + else + { + $primaryIP = array_flip($primaryDNSservers)[$primaryDNS]; + } + + // Validate primary IP + if (!filter_var($primaryIP, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === true) + { + $error = "Primary IP (".$primaryIP.") is invalid!"; + } + + // Get secondary DNS server IP address + if($secondaryDNS === "Custom") + { + $secondaryIP = $_POST["DNS2IP"]; + } + else + { + $secondaryIP = array_flip($secondaryDNSservers)[$secondaryDNS]; + } + + // Validate secondary IP + if (!filter_var($secondaryIP, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === true) + { + $error = "Secondary IP (".$secondaryIP.") is invalid!"; + } + + // If there has been no error we can save the new DNS server IPs + if(!isset($error)) + { + $cmd = "sudo pihole -a setdns ".$primaryIP." ".$secondaryIP; + exec($cmd); + } + + break; + + default: + + break; + } + } +?> diff --git a/settings.php b/settings.php index 02e12f6a..5acc3ac1 100644 --- a/settings.php +++ b/settings.php @@ -1,9 +1,14 @@ +
    +

    Debug output:

    +

    Error output:

    +
    +
    "Google", - "208.67.222.222" => "OpenDNS", - "4.2.2.1" => "Level3", - "199.85.126.10" => "Norton", - "8.26.56.26" => "Comodo" - ]; - - $secondaryDNSservers = [ - "8.8.4.4" => "Google", - "208.67.220.220" => "OpenDNS", - "4.2.2.2" => "Level3", - "199.85.127.10" => "Norton", - "8.20.247.20" => "Comodo" - ]; - if(isset($setupVars["PIHOLE_DNS_1"])){ if(isset($primaryDNSservers[$setupVars["PIHOLE_DNS_1"]])) { @@ -123,7 +112,8 @@
    checked>
    - + value="">
    @@ -137,15 +127,16 @@
    -
    checked>
    - value="">
    From db6e651c3281687bf4aa7d4d3b20241d73aa57b4 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 11 Dec 2016 18:21:36 +0100 Subject: [PATCH 11/58] Dynamically generate radio options for primary/secondary DNS servers according to the available servers in php/savesettiings.php --- settings.php | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/settings.php b/settings.php index 5acc3ac1..73900ab9 100644 --- a/settings.php +++ b/settings.php @@ -103,11 +103,7 @@
    -
    -
    -
    -
    -
    + $value) { ?>
    -
    -
    -
    -
    -
    + $value) { ?>
    Date: Sun, 11 Dec 2016 18:30:48 +0100 Subject: [PATCH 12/58] Enable/Disable query logging implemented --- php/savesettings.php | 14 ++++++++++++++ settings.php | 14 ++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/php/savesettings.php b/php/savesettings.php index 157ecd5e..6957f895 100644 --- a/php/savesettings.php +++ b/php/savesettings.php @@ -67,6 +67,20 @@ break; + // Set query logging + case "Logging": + + if($_POST["action"] === "Disable") + { + exec("sudo pihole -l off"); + } + else + { + exec("sudo pihole -l on"); + } + + break; + default: break; diff --git a/settings.php b/settings.php index 73900ab9..127753d8 100644 --- a/settings.php +++ b/settings.php @@ -155,10 +155,16 @@
    -
    -
    -
    - Note that disabling will render graphs on the web user interface useless +

    Current status: Enabled (recommended)Disabled

    + + + + + + + + +

    Note that disabling will render graphs on the web user interface useless

    From 7c1c45dbbd27d8de5c3a9fd0bccfcb18632017fb Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 11 Dec 2016 18:34:02 +0100 Subject: [PATCH 13/58] Simplified IP address validation --- php/savesettings.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/php/savesettings.php b/php/savesettings.php index 6957f895..c8ee5d1a 100644 --- a/php/savesettings.php +++ b/php/savesettings.php @@ -1,4 +1,9 @@ Date: Sun, 11 Dec 2016 19:29:23 +0100 Subject: [PATCH 14/58] Modifying excluded entries for top domains/top ads and top clients working --- php/savesettings.php | 69 ++++++++++++++++++++++++++++++++++++++++++++ settings.php | 13 +++++---- 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/php/savesettings.php b/php/savesettings.php index c8ee5d1a..a706fc9d 100644 --- a/php/savesettings.php +++ b/php/savesettings.php @@ -4,6 +4,15 @@ function validIP($ip){ return filter_var($secondaryIP, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false; } +// Credit: http://stackoverflow.com/a/4694816/2087442 +function validDomain($domain_name) +{ + $validChars = preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name); + $lengthCheck = preg_match("/^.{1,253}$/", $domain_name); + $labelLengthCheck = preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name); + return ( $validChars && $lengthCheck && $labelLengthCheck ); //length of each label +} + $debug = $_POST; $primaryDNSservers = [ @@ -86,6 +95,66 @@ function validIP($ip){ break; + // Set domains to be excludef from being shown in Top Domains (or Ads) and Top Clients + case "API": + + // Explode the contests of the textareas into PHP arrays + // \n (Unix) and \r\n (Win) will be considered as newline + // array_filter( ... ) will remove any empty lines + $domains = array_filter(preg_split('/\r\n|[\r\n]/', $_POST["domains"])); + $clients = array_filter(preg_split('/\r\n|[\r\n]/', $_POST["clients"])); + + $domainlist = ""; + $first = true; + foreach($domains as $domain) + { + if(!validDomain($domain)) + { + $error = "Entry ".$domain." is invalid!"; + break; + } + if(!$first) + { + $domainlist .= ","; + } + else + { + $first = false; + } + $domainlist .= $domain; + } + + $clientlist = ""; + $first = true; + foreach($clients as $client) + { + if(!validDomain($client)) + { + $error = "Entry ".$client." is invalid!"; + break; + } + if(!$first) + { + $clientlist .= ","; + } + else + { + $first = false; + } + $clientlist .= $client; + } + + if(!isset($error)) + { + // All entries are okay + $cmd = "sudo pihole -a setexcludedomains ".$domainlist; + exec($cmd); + $cmd = "sudo pihole -a setexcludeclients ".$clientlist; + exec($cmd); + } + + break; + default: break; diff --git a/settings.php b/settings.php index 127753d8..b1cb9c3b 100644 --- a/settings.php +++ b/settings.php @@ -171,17 +171,17 @@
    - +
    - +
    From 99ddf6cabd56878cf607c030792073563ff4a528 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 11 Dec 2016 19:37:15 +0100 Subject: [PATCH 15/58] Change temperature unit via settings page --- php/savesettings.php | 10 ++++++++++ settings.php | 8 ++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/php/savesettings.php b/php/savesettings.php index a706fc9d..151722f1 100644 --- a/php/savesettings.php +++ b/php/savesettings.php @@ -155,6 +155,16 @@ function validDomain($domain_name) break; + case "webUI": + if($_POST["tempunit"] == "F") + { + exec('sudo pihole -a -f'); + } + else + { + exec('sudo pihole -a -c'); + } + default: break; diff --git a/settings.php b/settings.php index b1cb9c3b..716f89f4 100644 --- a/settings.php +++ b/settings.php @@ -241,10 +241,14 @@

    CPU Temperature Unit

    -
    -
    +
    +
    +
    From 28f2c1588ddb4de2d88a01d1874777405041cb01 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 11 Dec 2016 19:40:39 +0100 Subject: [PATCH 16/58] Reread temperature unit from setupVars.conf --- settings.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/settings.php b/settings.php index 716f89f4..be51c66d 100644 --- a/settings.php +++ b/settings.php @@ -214,6 +214,17 @@
    +

    Web User Interface

    From a3efdab5df152a44871c01e421ef9f6ea064ff0e Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 11 Dec 2016 19:55:58 +0100 Subject: [PATCH 17/58] Small fix --- php/savesettings.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/php/savesettings.php b/php/savesettings.php index 151722f1..44da521d 100644 --- a/php/savesettings.php +++ b/php/savesettings.php @@ -1,7 +1,7 @@ Date: Sun, 11 Dec 2016 22:34:04 +0100 Subject: [PATCH 18/58] Added "restart system" --- js/other/jquery.confirm.min.js | 14 ++++++++++++++ js/pihole/settings.js | 17 +++++++++++++++++ php/savesettings.php | 4 ++++ settings.php | 3 ++- 4 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 js/other/jquery.confirm.min.js diff --git a/js/other/jquery.confirm.min.js b/js/other/jquery.confirm.min.js new file mode 100644 index 00000000..d86c13ed --- /dev/null +++ b/js/other/jquery.confirm.min.js @@ -0,0 +1,14 @@ +/*! + * jquery.confirm + * + * @version 2.3.1 + * + * @author My C-Labs + * @author Matthieu Napoli + * @author Russel Vela + * @author Marcus Schwarz + * + * @license MIT + * @url https://myclabs.github.io/jquery.confirm/ + */ +(function(a){a.fn.confirm=function(b){if(typeof b==="undefined"){b={}}this.click(function(c){c.preventDefault();var d=a.extend({button:a(this)},b);a.confirm(d,c)});return this};a.confirm=function(k,g){if(typeof k=="undefined"){console.error("No options given.");return}if(a(".confirmation-modal").length>0){return}var j={};if(k.button){var c={title:"title",text:"text","confirm-button":"confirmButton","submit-form":"submitForm","cancel-button":"cancelButton","confirm-button-class":"confirmButtonClass","cancel-button-class":"cancelButtonClass","dialog-class":"dialogClass","modal-options-backdrop":"modalOptionsBackdrop","modal-options-keyboard":"modalOptionsKeyboard"};a.each(c,function(e,l){var m=k.button.data(e);if(typeof m!="undefined"){j[l]=m}})}var d=a.extend({},a.confirm.options,{confirm:function(){if(j.submitForm||(typeof j.submitForm=="undefined"&&k.submitForm)||(typeof j.submitForm=="undefined"&&typeof k.submitForm=="undefined"&&a.confirm.options.submitForm)){g.target.closest("form").submit()}else{var e=g&&(("string"===typeof g&&g)||(g.currentTarget&&g.currentTarget.attributes.href.value));if(e){if(k.post){var l=a('
    ');a("body").append(l);l.submit()}else{window.location=e}}}},cancel:function(e){},button:null},k,j);var b="";if(d.title!==""){b='"}var h="";if(d.cancelButton){h='"}var f='";var i=a(f);if(typeof d.modalOptionsBackdrop!="undefined"||typeof d.modalOptionsKeyboard!="undefined"){i.modal({backdrop:d.modalOptionsBackdrop,keyboard:d.modalOptionsKeyboard})}i.on("shown.bs.modal",function(){i.find(".btn-primary:first").focus()});i.on("hidden.bs.modal",function(){i.remove()});i.find(".confirm").click(function(){d.confirm(d.button)});i.find(".cancel").click(function(){d.cancel(d.button)});a("body").append(i);i.modal("show")};a.confirm.options={text:"Are you sure?",title:"",confirmButton:"Yes",cancelButton:"Cancel",post:false,submitForm:false,confirmButtonClass:"btn-primary",cancelButtonClass:"btn-default",dialogClass:"modal-dialog",modalOptionsBackdrop:true,modalOptionsKeyboard:true}})(jQuery); diff --git a/js/pihole/settings.js b/js/pihole/settings.js index 5160e9b3..e6d77945 100644 --- a/js/pihole/settings.js +++ b/js/pihole/settings.js @@ -1,3 +1,20 @@ $(function () { $("[data-mask]").inputmask(); }); + +$(".confirm-reboot").confirm({ + text: "Are you sure you want to send a reboot command to your Pi-Hole?", + title: "Confirmation required", + confirm: function(button) { + $.post( "php/savesettings.php", { "field": "reboot" } ); + }, + cancel: function(button) { + // nothing to do + }, + confirmButton: "Yes, reboot", + cancelButton: "No, go back", + post: true, + confirmButtonClass: "btn-danger", + cancelButtonClass: "btn-success", + dialogClass: "modal-dialog modal-lg" // Bootstrap classes for large modal +}); diff --git a/php/savesettings.php b/php/savesettings.php index 44da521d..12d2f4c1 100644 --- a/php/savesettings.php +++ b/php/savesettings.php @@ -165,6 +165,10 @@ function validDomain($domain_name) exec('sudo pihole -a -c'); } + case "reboot": + exec("sudo pihole -a reboot"); + break; + default: break; diff --git a/settings.php b/settings.php index be51c66d..0c172087 100644 --- a/settings.php +++ b/settings.php @@ -281,7 +281,7 @@

    System Administration

    - +
    @@ -294,5 +294,6 @@ ?> + From 0a40091f4a3ccb00b29581f5c5b86d2b560c6535 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Dec 2016 10:33:12 +0100 Subject: [PATCH 19/58] Use method shorthands --- js/pihole/settings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/pihole/settings.js b/js/pihole/settings.js index e6d77945..beeb2dcb 100644 --- a/js/pihole/settings.js +++ b/js/pihole/settings.js @@ -5,10 +5,10 @@ $(function () { $(".confirm-reboot").confirm({ text: "Are you sure you want to send a reboot command to your Pi-Hole?", title: "Confirmation required", - confirm: function(button) { + confirm(button) { $.post( "php/savesettings.php", { "field": "reboot" } ); }, - cancel: function(button) { + cancel(button) { // nothing to do }, confirmButton: "Yes, reboot", From 498d0b9b36f9639068ce64550e88bec877fe28b5 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Dec 2016 10:38:07 +0100 Subject: [PATCH 20/58] Add "restart DNS server" --- js/pihole/settings.js | 19 ++++++++++++++++++- php/savesettings.php | 4 ++++ settings.php | 2 +- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/js/pihole/settings.js b/js/pihole/settings.js index beeb2dcb..22de261c 100644 --- a/js/pihole/settings.js +++ b/js/pihole/settings.js @@ -16,5 +16,22 @@ $(".confirm-reboot").confirm({ post: true, confirmButtonClass: "btn-danger", cancelButtonClass: "btn-success", - dialogClass: "modal-dialog modal-lg" // Bootstrap classes for large modal + dialogClass: "modal-dialog modal-mg" // Bootstrap classes for mid-size modal +}); + +$(".confirm-restartdns").confirm({ + text: "Are you sure you want to send a restart command to your DNS server?", + title: "Confirmation required", + confirm(button) { + $.post( "php/savesettings.php", { "field": "restartdns" } ); + }, + cancel(button) { + // nothing to do + }, + confirmButton: "Yes, restart DNS", + cancelButton: "No, go back", + post: true, + confirmButtonClass: "btn-danger", + cancelButtonClass: "btn-success", + dialogClass: "modal-dialog modal-mg" }); diff --git a/php/savesettings.php b/php/savesettings.php index 12d2f4c1..029d293e 100644 --- a/php/savesettings.php +++ b/php/savesettings.php @@ -169,6 +169,10 @@ function validDomain($domain_name) exec("sudo pihole -a reboot"); break; + case "restartdns": + exec("sudo pihole -a restartdns"); + break; + default: break; diff --git a/settings.php b/settings.php index 0c172087..2e5efab9 100644 --- a/settings.php +++ b/settings.php @@ -282,7 +282,7 @@
    - +
    From 6ffbdc038efc4be59c5837eb8e98de244c2a9556 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Dec 2016 10:43:05 +0100 Subject: [PATCH 21/58] Added "flush logs" --- js/pihole/settings.js | 17 +++++++++++++++++ php/savesettings.php | 4 ++++ settings.php | 2 +- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/js/pihole/settings.js b/js/pihole/settings.js index 22de261c..c118c9da 100644 --- a/js/pihole/settings.js +++ b/js/pihole/settings.js @@ -35,3 +35,20 @@ $(".confirm-restartdns").confirm({ cancelButtonClass: "btn-success", dialogClass: "modal-dialog modal-mg" }); + +$(".confirm-flushlogs").confirm({ + text: "By default, the log is flushed at the end of the day via cron, but a very large log file can slow down the Web interface, so flushing it can be useful. Note that your statistics will be reset and you lose the statistics up to this point. Are you sure you want to flush your logs?", + title: "Confirmation required", + confirm(button) { + $.post( "php/savesettings.php", { "field": "flushlogs" } ); + }, + cancel(button) { + // nothing to do + }, + confirmButton: "Yes, flush logs", + cancelButton: "No, go back", + post: true, + confirmButtonClass: "btn-danger", + cancelButtonClass: "btn-success", + dialogClass: "modal-dialog modal-mg" +}); diff --git a/php/savesettings.php b/php/savesettings.php index 029d293e..5dd0d7f5 100644 --- a/php/savesettings.php +++ b/php/savesettings.php @@ -173,6 +173,10 @@ function validDomain($domain_name) exec("sudo pihole -a restartdns"); break; + case "flushlogs": + exec("sudo pihole -f"); + break; + default: break; diff --git a/settings.php b/settings.php index 2e5efab9..d65ec18a 100644 --- a/settings.php +++ b/settings.php @@ -283,7 +283,7 @@
    - +
    From 0af1e91b1280be5e221cca58165560796ce0ee2a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Dec 2016 10:43:38 +0100 Subject: [PATCH 22/58] Removed "flush logs" from Help Center --- help.php | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/help.php b/help.php index 44cf1896..5a0976da 100644 --- a/help.php +++ b/help.php @@ -136,21 +136,8 @@ Shows the currently installed Pi-hole and Web Interface version. If an update is available, this will be indicated here
    -
    -
    -

    Emergency help

    - Depending on your system and how heavily your Pi-hole is used, you may want to flush the log throughout the day by clicking on FLUSH. By default, the log if flushed at the end of the day via cron, but a very large log file can slow down the Web interface, so flushing it can be useful. - Note that your statistics will be reset and you lose the statistics up to this point. -
    -
    From 1c34dae7778a2f151ce5623a59b3ce10bc16a7c9 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Dec 2016 10:51:43 +0100 Subject: [PATCH 23/58] Removed what we currently don't support --- settings.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/settings.php b/settings.php index d65ec18a..02aa3b49 100644 --- a/settings.php +++ b/settings.php @@ -231,6 +231,7 @@
    +Query Log Page
    @@ -250,6 +251,7 @@
    +*/ ?>

    CPU Temperature Unit

    @@ -262,6 +264,7 @@
    +

    Blocking Page

    @@ -276,6 +279,7 @@
    +*/ ?>

    System Administration

    From 68a35091a8b5d95b4218231e3c88a155381ab101 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Dec 2016 13:14:45 +0100 Subject: [PATCH 24/58] Add "query log" options --- php/savesettings.php | 19 +++++++++++++++++++ settings.php | 13 +++++++++++++ 2 files changed, 32 insertions(+) diff --git a/php/savesettings.php b/php/savesettings.php index 5dd0d7f5..b51b9c2d 100644 --- a/php/savesettings.php +++ b/php/savesettings.php @@ -144,6 +144,7 @@ function validDomain($domain_name) $clientlist .= $client; } + // Set Top Lists options if(!isset($error)) { // All entries are okay @@ -153,6 +154,24 @@ function validDomain($domain_name) exec($cmd); } + // Set query log options + if(isset($_POST["querylog-permitted"]) && isset($_POST["querylog-blocked"])) + { + exec("sudo pihole -a setquerylog all"); + } + elseif(isset($_POST["querylog-permitted"])) + { + exec("sudo pihole -a setquerylog permittedonly"); + } + elseif(isset($_POST["querylog-blocked"])) + { + exec("sudo pihole -a setquerylog blockedonly"); + } + else + { + exec("sudo pihole -a setquerylog none"); + } + break; case "webUI": diff --git a/settings.php b/settings.php index 02aa3b49..516e197a 100644 --- a/settings.php +++ b/settings.php @@ -185,6 +185,14 @@ } else { $excludedClients = ""; } + + // Exluded clients + if(isset($setupVars["API_QUERY_LOG_SHOW"])) + { + $queryLog = $setupVars["API_QUERY_LOG_SHOW"]; + } else { + $queryLog = "all"; + } ?>
    @@ -207,6 +215,11 @@
    +

    Query Log

    +
    +
    +
    +
    + +
    +
    +

    Pi-Hole DHCP Server

    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    From
    + +
    +
    +
    +
    +
    To
    + +
    +
    +
    + +
    +
    +
    +
    Router
    + +
    +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    + +
    -

    Current status: Enabled (recommended)Disabled

    - - - - - - - - -

    Note that disabling will render graphs on the web user interface useless

    +

    Current status: + + Enabled (recommended) + + Disabled +

    + + +

    Note that disabling will render graphs on the web user interface useless

    +
    +
    - -

    API

    From 7d4234b208e2451594c4e69b36e67e0434269428 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Dec 2016 15:37:39 +0100 Subject: [PATCH 28/58] Removed debug statement --- js/pihole/settings.js | 1 - settings.php | 12 ++++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/js/pihole/settings.js b/js/pihole/settings.js index 44d4b003..ada579f1 100644 --- a/js/pihole/settings.js +++ b/js/pihole/settings.js @@ -55,5 +55,4 @@ $(".confirm-flushlogs").confirm({ $("#DHCPchk").click(function() { $("input.DHCPgroup").prop("disabled", !this.checked); - console.log(this.checked); }); diff --git a/settings.php b/settings.php index d272ffac..0ae3cf09 100644 --- a/settings.php +++ b/settings.php @@ -101,10 +101,8 @@
    +
    -
    - -
    From
    @@ -117,19 +115,17 @@
    -
    - -
    +
    Router
    -
    +

    +
    -
    From 8dea523dbdb87f54bc96cdcc3d973484f187b955 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Dec 2016 16:09:44 +0100 Subject: [PATCH 29/58] Exchanged -
    +
    +
    From c93995167720e6a7f53bab286e668c0a6fdd3296 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Dec 2016 17:36:31 +0100 Subject: [PATCH 30/58] Print size of Pi-Hole log file --- php/savesettings.php | 31 +++++++++++++++++++++++++++++++ settings.php | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/php/savesettings.php b/php/savesettings.php index 30cb1525..5274bfc8 100644 --- a/php/savesettings.php +++ b/php/savesettings.php @@ -241,4 +241,35 @@ function validDomain($domain_name) break; } } + + // Credit: http://stackoverflow.com/a/5501447/2087442 + function formatSizeUnits($bytes) + { + if ($bytes >= 1073741824) + { + $bytes = number_format($bytes / 1073741824, 2) . ' GB'; + } + elseif ($bytes >= 1048576) + { + $bytes = number_format($bytes / 1048576, 2) . ' MB'; + } + elseif ($bytes >= 1024) + { + $bytes = number_format($bytes / 1024, 2) . ' kB'; + } + elseif ($bytes > 1) + { + $bytes = $bytes . ' bytes'; + } + elseif ($bytes == 1) + { + $bytes = $bytes . ' byte'; + } + else + { + $bytes = '0 bytes'; + } + + return $bytes; + } ?> diff --git a/settings.php b/settings.php index 10c2e597..85622b6b 100644 --- a/settings.php +++ b/settings.php @@ -225,7 +225,7 @@ ?>
    -

    Query Logging

    +

    Query Logging (size of log )

    From 83f64508eaab524d5dbb3858a886fd1635691658 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Dec 2016 18:11:41 +0100 Subject: [PATCH 31/58] Added DataTables for DHCP leases --- js/pihole/settings.js | 7 +++++++ settings.php | 37 +++++++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/js/pihole/settings.js b/js/pihole/settings.js index ada579f1..f47562ce 100644 --- a/js/pihole/settings.js +++ b/js/pihole/settings.js @@ -56,3 +56,10 @@ $(".confirm-flushlogs").confirm({ $("#DHCPchk").click(function() { $("input.DHCPgroup").prop("disabled", !this.checked); }); + +$(document).ready(function() { + if(!!document.getElementById("DHCPLeasesTable")) + { + $('#DHCPLeasesTable').DataTable(); + } +} ); diff --git a/settings.php b/settings.php index 85622b6b..78dd9723 100644 --- a/settings.php +++ b/settings.php @@ -122,11 +122,44 @@

    - + 1) + { + array_push($dhcp_leases,["MAC"=>$line[1], "IP"=>$line[2], "NAME"=>$line[3]]); + } + + // // Sort $dhcpleases by IP (ASC) + // usort($dhcp_leases, function ($a, $b) { + // $explodea = explode(".",$a["IP"]); + // $explodeb = explode(".",$b["IP"]); + // if ($explodea == $explodeb) return 0; + // return ($explodea < $explodeb) ? -1 : 1; + // }); + } + ?>
    -
    + + + + + + + + + + + +
    IP addressHostnameMAC address
    From f74a90f65f8e5bde96b5eee4a5d585fb5f136595 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Dec 2016 18:11:59 +0100 Subject: [PATCH 32/58] Removed obsolete sorting algorithm (datatables will do the sorting for us) --- settings.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/settings.php b/settings.php index 78dd9723..2a59f0f2 100644 --- a/settings.php +++ b/settings.php @@ -135,14 +135,6 @@ { array_push($dhcp_leases,["MAC"=>$line[1], "IP"=>$line[2], "NAME"=>$line[3]]); } - - // // Sort $dhcpleases by IP (ASC) - // usort($dhcp_leases, function ($a, $b) { - // $explodea = explode(".",$a["IP"]); - // $explodeb = explode(".",$b["IP"]); - // if ($explodea == $explodeb) return 0; - // return ($explodea < $explodeb) ? -1 : 1; - // }); } ?> From 29ff6310f4058ab1ef513d24fa46001612910d5a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Dec 2016 18:16:08 +0100 Subject: [PATCH 33/58] Modified DOM of table --- js/pihole/settings.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/js/pihole/settings.js b/js/pihole/settings.js index f47562ce..75ac43fe 100644 --- a/js/pihole/settings.js +++ b/js/pihole/settings.js @@ -60,6 +60,11 @@ $("#DHCPchk").click(function() { $(document).ready(function() { if(!!document.getElementById("DHCPLeasesTable")) { - $('#DHCPLeasesTable').DataTable(); + $('#DHCPLeasesTable').DataTable({ + dom: "<'row'<'col-sm-12'i>>" + + "<'row'<'col-sm-12'tr>>" + + "<'row'<'col-sm-5'f><'col-sm-7'p>>", + "pageLength": 5 + }); } } ); From fcbf6f5c4c135ae08aa2d7baff236fb784b00083 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Dec 2016 18:48:04 +0100 Subject: [PATCH 34/58] Prevent direct access to savesettings.php --- php/savesettings.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/php/savesettings.php b/php/savesettings.php index 5274bfc8..dd280572 100644 --- a/php/savesettings.php +++ b/php/savesettings.php @@ -1,5 +1,10 @@ Date: Mon, 12 Dec 2016 18:54:02 +0100 Subject: [PATCH 35/58] Disable DHCP input fields if DHCP server checkbox is not checked --- settings.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/settings.php b/settings.php index 2a59f0f2..dd20a5f9 100644 --- a/settings.php +++ b/settings.php @@ -106,20 +106,20 @@
    From
    - + disabled>
    To
    - + disabled>
    Router
    - + disabled>

    Date: Tue, 13 Dec 2016 12:02:02 +0100 Subject: [PATCH 36/58] Fixed codacy issue --- js/pihole/settings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/pihole/settings.js b/js/pihole/settings.js index 75ac43fe..914af565 100644 --- a/js/pihole/settings.js +++ b/js/pihole/settings.js @@ -58,9 +58,9 @@ $("#DHCPchk").click(function() { }); $(document).ready(function() { - if(!!document.getElementById("DHCPLeasesTable")) + if(document.getElementById("DHCPLeasesTable")) { - $('#DHCPLeasesTable').DataTable({ + $("#DHCPLeasesTable").DataTable({ dom: "<'row'<'col-sm-12'i>>" + "<'row'<'col-sm-12'tr>>" + "<'row'<'col-sm-5'f><'col-sm-7'p>>", From ec2ad64cc43ea7ebf12bbdb6791c89bac8b2d5b7 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 13 Dec 2016 12:08:38 +0100 Subject: [PATCH 37/58] Make $error accumulate all error messages --- php/savesettings.php | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/php/savesettings.php b/php/savesettings.php index dd280572..fa676ece 100644 --- a/php/savesettings.php +++ b/php/savesettings.php @@ -45,6 +45,8 @@ function validDomain($domain_name) $primaryDNS = $_POST["primaryDNS"]; $secondaryDNS = $_POST["secondaryDNS"]; + $error = ""; + // Get primary DNS server IP address if($primaryDNS === "Custom") { @@ -58,7 +60,7 @@ function validDomain($domain_name) // Validate primary IP if (!validIP($primaryIP)) { - $error = "Primary IP (".$primaryIP.") is invalid!"; + $error .= "Primary IP (".$primaryIP.") is invalid! "; } // Get secondary DNS server IP address @@ -74,11 +76,11 @@ function validDomain($domain_name) // Validate secondary IP if (!validIP($secondaryIP)) { - $error = "Secondary IP (".$secondaryIP.") is invalid!"; + $error .= "Secondary IP (".$secondaryIP.") is invalid!"; } // If there has been no error we can save the new DNS server IPs - if(!isset($error)) + if(!strlen($error)) { $cmd = "sudo pihole -a setdns ".$primaryIP." ".$secondaryIP; exec($cmd); @@ -110,12 +112,13 @@ function validDomain($domain_name) $clients = array_filter(preg_split('/\r\n|[\r\n]/', $_POST["clients"])); $domainlist = ""; + $error = ""; $first = true; foreach($domains as $domain) { if(!validDomain($domain)) { - $error = "Entry ".$domain." is invalid!"; + $error .= "Top Domains/Ads entry ".$domain." is invalid! "; break; } if(!$first) @@ -135,7 +138,7 @@ function validDomain($domain_name) { if(!validDomain($client)) { - $error = "Entry ".$client." is invalid!"; + $error .= "Top Clients entry ".$client." is invalid! "; break; } if(!$first) @@ -150,7 +153,7 @@ function validDomain($domain_name) } // Set Top Lists options - if(!isset($error)) + if(!strlen($error)) { // All entries are okay $cmd = "sudo pihole -a setexcludedomains ".$domainlist; @@ -205,29 +208,30 @@ function validDomain($domain_name) if(isset($_POST["active"])) { + $error = ""; // Validate from IP $from = $_POST["from"]; if (!validIP($from)) { - $error = "From IP (".$from.") is invalid!"; + $error .= "From IP (".$from.") is invalid! "; } // Validate to IP $to = $_POST["to"]; if (!validIP($to)) { - $error = "To IP (".$to.") is invalid!"; + $error .= "To IP (".$to.") is invalid! "; } // Validate router IP $router = $_POST["router"]; if (!validIP($router)) { - $error = "Router IP (".$router.") is invalid!"; + $error .= "Router IP (".$router.") is invalid! "; } $cmd = "sudo pihole -a enabledhcp ".$from." ".$to." ".$router; - if(!isset($error)) + if(!strlen($error)) { exec($cmd); } From 9cff0d41bcc24f1391c02ad0ceb564a68c887267 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 13 Dec 2016 12:09:02 +0100 Subject: [PATCH 38/58] Fix wrong comment --- header.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/header.php b/header.php index 29daeb07..4ee71147 100644 --- a/header.php +++ b/header.php @@ -273,7 +273,7 @@ echo '
  • Enable
  • '; } ?> - +
  • Settings From ff5709b97fc2d370803d2aab5657bf34bf4b441c Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 13 Dec 2016 12:13:35 +0100 Subject: [PATCH 39/58] Some simplificaton and fixes for php/savesettings.php --- php/savesettings.php | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/php/savesettings.php b/php/savesettings.php index fa676ece..022a9e54 100644 --- a/php/savesettings.php +++ b/php/savesettings.php @@ -82,8 +82,7 @@ function validDomain($domain_name) // If there has been no error we can save the new DNS server IPs if(!strlen($error)) { - $cmd = "sudo pihole -a setdns ".$primaryIP." ".$secondaryIP; - exec($cmd); + exec("sudo pihole -a setdns ".$primaryIP." ".$secondaryIP); } break; @@ -102,10 +101,10 @@ function validDomain($domain_name) break; - // Set domains to be excludef from being shown in Top Domains (or Ads) and Top Clients + // Set domains to be excluded from being shown in Top Domains (or Ads) and Top Clients case "API": - // Explode the contests of the textareas into PHP arrays + // Explode the contents of the textareas into PHP arrays // \n (Unix) and \r\n (Win) will be considered as newline // array_filter( ... ) will remove any empty lines $domains = array_filter(preg_split('/\r\n|[\r\n]/', $_POST["domains"])); @@ -156,10 +155,8 @@ function validDomain($domain_name) if(!strlen($error)) { // All entries are okay - $cmd = "sudo pihole -a setexcludedomains ".$domainlist; - exec($cmd); - $cmd = "sudo pihole -a setexcludeclients ".$clientlist; - exec($cmd); + exec("sudo pihole -a setexcludedomains ".$domainlist); + exec("sudo pihole -a setexcludeclients ".$clientlist); } // Set query log options @@ -230,10 +227,9 @@ function validDomain($domain_name) $error .= "Router IP (".$router.") is invalid! "; } - $cmd = "sudo pihole -a enabledhcp ".$from." ".$to." ".$router; if(!strlen($error)) { - exec($cmd); + exec("sudo pihole -a enabledhcp ".$from." ".$to." ".$router); } } else @@ -241,8 +237,6 @@ function validDomain($domain_name) exec("sudo pihole -a disabledhcp"); } - // $error = $cmd; - break; default: From 18aac58a8c4341e77cefc6b79b43c3b554f3dbf7 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 13 Dec 2016 12:24:09 +0100 Subject: [PATCH 40/58] Client entries should actually be IP addresses + Fixed some typos --- php/savesettings.php | 4 ++-- settings.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/php/savesettings.php b/php/savesettings.php index 022a9e54..df105320 100644 --- a/php/savesettings.php +++ b/php/savesettings.php @@ -135,9 +135,9 @@ function validDomain($domain_name) $first = true; foreach($clients as $client) { - if(!validDomain($client)) + if(!validIP($client)) { - $error .= "Top Clients entry ".$client." is invalid! "; + $error .= "Top Clients entry ".$client." is invalid (use only IP addresses)! "; break; } if(!$first) diff --git a/settings.php b/settings.php index dd20a5f9..7ad7373f 100644 --- a/settings.php +++ b/settings.php @@ -253,7 +253,6 @@

    Query Logging (size of log )

    -

    Current status: Enabled (recommended) @@ -264,9 +263,9 @@

    Note that disabling will render graphs on the web user interface useless

    -
    - +

    Query Log

    From c9479f1e18a43a4f90a3dfbbd22881906c2db14e Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 13 Dec 2016 12:28:19 +0100 Subject: [PATCH 41/58] Modified ajax calls to actual form submits --- js/pihole/settings.js | 6 +++--- settings.php | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/js/pihole/settings.js b/js/pihole/settings.js index 914af565..dd751ac1 100644 --- a/js/pihole/settings.js +++ b/js/pihole/settings.js @@ -6,7 +6,7 @@ $(".confirm-reboot").confirm({ text: "Are you sure you want to send a reboot command to your Pi-Hole?", title: "Confirmation required", confirm(button) { - $.post( "php/savesettings.php", { "field": "reboot" } ); + $("#rebootform").submit(); }, cancel(button) { // nothing to do @@ -23,7 +23,7 @@ $(".confirm-restartdns").confirm({ text: "Are you sure you want to send a restart command to your DNS server?", title: "Confirmation required", confirm(button) { - $.post( "php/savesettings.php", { "field": "restartdns" } ); + $("#restartdnsform").submit(); }, cancel(button) { // nothing to do @@ -40,7 +40,7 @@ $(".confirm-flushlogs").confirm({ text: "By default, the log is flushed at the end of the day via cron, but a very large log file can slow down the Web interface, so flushing it can be useful. Note that your statistics will be reset and you lose the statistics up to this point. Are you sure you want to flush your logs?", title: "Confirmation required", confirm(button) { - $.post( "php/savesettings.php", { "field": "flushlogs" } ); + $("#flushlogsform").submit(); }, cancel(button) { // nothing to do diff --git a/settings.php b/settings.php index 7ad7373f..68535454 100644 --- a/settings.php +++ b/settings.php @@ -408,6 +408,16 @@ + +
    + +
    +
    + +
    +
    + +
    From 6fae811f82f95447efd4e4d34b7c5e6f167e0326 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 13 Dec 2016 12:42:24 +0100 Subject: [PATCH 42/58] Fix one comment --- settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.php b/settings.php index 68535454..f9f636a2 100644 --- a/settings.php +++ b/settings.php @@ -286,7 +286,7 @@ $excludedDomains = ""; } - // Exluded clients + // Exluded clients in API Query Log call if(isset($setupVars["API_EXCLUDE_CLIENTS"])) { $excludedClients = explode(",", $setupVars["API_EXCLUDE_CLIENTS"]); From 07987b5a04475baff18a219ab44f3d10f280bd82 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 13 Dec 2016 12:56:04 +0100 Subject: [PATCH 43/58] Add info boxes --- js/pihole/settings.js | 7 +++++++ php/savesettings.php | 3 +++ settings.php | 7 +++++++ 3 files changed, 17 insertions(+) diff --git a/js/pihole/settings.js b/js/pihole/settings.js index dd751ac1..67c60099 100644 --- a/js/pihole/settings.js +++ b/js/pihole/settings.js @@ -68,3 +68,10 @@ $(document).ready(function() { }); } } ); + +// Handle hiding of alerts +$(function(){ + $("[data-hide]").on("click", function(){ + $(this).closest("." + $(this).attr("data-hide")).hide(); + }); +}); diff --git a/php/savesettings.php b/php/savesettings.php index df105320..f0581b7f 100644 --- a/php/savesettings.php +++ b/php/savesettings.php @@ -191,14 +191,17 @@ function validDomain($domain_name) case "reboot": exec("sudo pihole -a reboot"); + $success = "The system will reboot in 5 seconds..."; break; case "restartdns": exec("sudo pihole -a restartdns"); + $success = "The DNS server has been restarted"; break; case "flushlogs": exec("sudo pihole -f"); + $success = "The Pi-Hole log file has been flushed"; break; case "DHCP": diff --git a/settings.php b/settings.php index f9f636a2..2766bcc8 100644 --- a/settings.php +++ b/settings.php @@ -4,6 +4,13 @@ // Reread ini file as things might have been changed $setupVars = parse_ini_file("/etc/pihole/setupVars.conf"); ?> + + + + +

    Debug output:

    Error output:

    From 30142816ac42009f5e789d737f60feee56927333 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 13 Dec 2016 12:56:49 +0100 Subject: [PATCH 44/58] Properly handle if the DHCP leases file has not been found (show "No data available" in the table) --- settings.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/settings.php b/settings.php index 2766bcc8..4d9bbd70 100644 --- a/settings.php +++ b/settings.php @@ -132,10 +132,11 @@ 1) From b28f4a09319a2595c37128d0c95450c690ee1c0a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 13 Dec 2016 13:08:27 +0100 Subject: [PATCH 45/58] Add info/error boxes and populate with content. If there are hosts with errors in Top Lists / Top Clients each error will be shown separately --- php/savesettings.php | 28 +++++++++++++++++++--------- settings.php | 18 ++++++++++++------ 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/php/savesettings.php b/php/savesettings.php index f0581b7f..c50d813d 100644 --- a/php/savesettings.php +++ b/php/savesettings.php @@ -60,7 +60,7 @@ function validDomain($domain_name) // Validate primary IP if (!validIP($primaryIP)) { - $error .= "Primary IP (".$primaryIP.") is invalid! "; + $error .= "Primary IP (".$primaryIP.") is invalid!
    "; } // Get secondary DNS server IP address @@ -76,13 +76,14 @@ function validDomain($domain_name) // Validate secondary IP if (!validIP($secondaryIP)) { - $error .= "Secondary IP (".$secondaryIP.") is invalid!"; + $error .= "Secondary IP (".$secondaryIP.") is invalid!
    "; } // If there has been no error we can save the new DNS server IPs if(!strlen($error)) { exec("sudo pihole -a setdns ".$primaryIP." ".$secondaryIP); + $success = "The DNS settings have been updated"; } break; @@ -93,10 +94,12 @@ function validDomain($domain_name) if($_POST["action"] === "Disable") { exec("sudo pihole -l off"); + $success = "Logging has been disabled"; } else { exec("sudo pihole -l on"); + $success = "Logging has been enabled"; } break; @@ -117,8 +120,7 @@ function validDomain($domain_name) { if(!validDomain($domain)) { - $error .= "Top Domains/Ads entry ".$domain." is invalid! "; - break; + $error .= "Top Domains/Ads entry ".$domain." is invalid!
    "; } if(!$first) { @@ -137,8 +139,7 @@ function validDomain($domain_name) { if(!validIP($client)) { - $error .= "Top Clients entry ".$client." is invalid (use only IP addresses)! "; - break; + $error .= "Top Clients entry ".$client." is invalid (use only IP addresses)!
    "; } if(!$first) { @@ -157,24 +158,29 @@ function validDomain($domain_name) // All entries are okay exec("sudo pihole -a setexcludedomains ".$domainlist); exec("sudo pihole -a setexcludeclients ".$clientlist); + $success = "The API settings have been updated
    "; } // Set query log options if(isset($_POST["querylog-permitted"]) && isset($_POST["querylog-blocked"])) { exec("sudo pihole -a setquerylog all"); + $success .= "All entries will be shown in Query Log"; } elseif(isset($_POST["querylog-permitted"])) { exec("sudo pihole -a setquerylog permittedonly"); + $success .= "Only permitted will be shown in Query Log"; } elseif(isset($_POST["querylog-blocked"])) { exec("sudo pihole -a setquerylog blockedonly"); + $success .= "Only blocked entries will be shown in Query Log"; } else { exec("sudo pihole -a setquerylog none"); + $success .= "No entries will be shown in Query Log"; } break; @@ -183,10 +189,12 @@ function validDomain($domain_name) if($_POST["tempunit"] == "F") { exec('sudo pihole -a -f'); + $success = "The webUI settings have been updated"; } else { exec('sudo pihole -a -c'); + $success = "The webUI settings have been updated"; } case "reboot": @@ -213,31 +221,33 @@ function validDomain($domain_name) $from = $_POST["from"]; if (!validIP($from)) { - $error .= "From IP (".$from.") is invalid! "; + $error .= "From IP (".$from.") is invalid!
    "; } // Validate to IP $to = $_POST["to"]; if (!validIP($to)) { - $error .= "To IP (".$to.") is invalid! "; + $error .= "To IP (".$to.") is invalid!
    "; } // Validate router IP $router = $_POST["router"]; if (!validIP($router)) { - $error .= "Router IP (".$router.") is invalid! "; + $error .= "Router IP (".$router.") is invalid!
    "; } if(!strlen($error)) { exec("sudo pihole -a enabledhcp ".$from." ".$to." ".$router); + $success = "The DHCP server has been activated"; } } else { exec("sudo pihole -a disabledhcp"); + $success = "The DHCP server has been deactivated"; } break; diff --git a/settings.php b/settings.php index 4d9bbd70..25fc97b6 100644 --- a/settings.php +++ b/settings.php @@ -5,16 +5,23 @@ $setupVars = parse_ini_file("/etc/pihole/setupVars.conf"); ?> - + -
    -

    Debug output:

    -

    Error output:

    + 0){ ?> + + + + + +
    @@ -267,14 +274,13 @@ Disabled

    -

    Note that disabling will render graphs on the web user interface useless