WP Statistics - Version 12.6.4

Version Description

  • Added: The Visitor IP configuration in the setting page for choosing method that gets visitor's IP addresses.
  • Improved: Minor issues in datepicker.
  • Improved: Minor issues in search referrer.
Download this release

Release Info

Developer mostafa.s1990
Plugin Icon 128x128 WP Statistics
Version 12.6.4
Comparing to
See all releases

Code changes from version 12.6.3 to 12.6.4

Files changed (40) hide show
  1. assets/images/welcome/topcountry-widget.png +0 -0
  2. assets/images/welcome/topreferring-widget.png +0 -0
  3. assets/images/welcome/usersonline-widget.png +0 -0
  4. assets/images/welcome/visitor-ip.png +0 -0
  5. assets/js/moment.min.js +1 -0
  6. composer.json +1 -2
  7. composer.lock +481 -0
  8. includes/classes/class-wp-statistics-admin.php +1 -0
  9. includes/classes/class-wp-statistics-hits.php +1 -1
  10. includes/classes/class-wp-statistics.php +59 -7
  11. includes/functions/functions.php +75 -10
  12. includes/log/top-visitors.php +7 -7
  13. includes/settings/tabs/wps-visitor-ip.php +163 -0
  14. includes/settings/wps-settings.php +4 -0
  15. includes/templates/welcome.php +3 -44
  16. includes/vendor/composer/autoload_psr4.php +0 -3
  17. includes/vendor/composer/autoload_static.php +0 -18
  18. includes/vendor/composer/installed.json +6 -64
  19. includes/vendor/erusev/parsedown/Parsedown.php +16 -2
  20. includes/vendor/vectorface/whip/.dunitconfig +0 -19
  21. includes/vendor/vectorface/whip/.gitignore +0 -4
  22. includes/vendor/vectorface/whip/.scrutinizer.yml +0 -6
  23. includes/vendor/vectorface/whip/.travis.yml +0 -29
  24. includes/vendor/vectorface/whip/LICENSE +0 -21
  25. includes/vendor/vectorface/whip/README.md +0 -237
  26. includes/vendor/vectorface/whip/composer.json +0 -42
  27. includes/vendor/vectorface/whip/phpunit.xml +0 -21
  28. includes/vendor/vectorface/whip/src/IpRange/IpRange.php +0 -43
  29. includes/vendor/vectorface/whip/src/IpRange/IpWhitelist.php +0 -121
  30. includes/vendor/vectorface/whip/src/IpRange/Ipv4Range.php +0 -152
  31. includes/vendor/vectorface/whip/src/IpRange/Ipv6Range.php +0 -122
  32. includes/vendor/vectorface/whip/src/Request/Psr7RequestAdapter.php +0 -74
  33. includes/vendor/vectorface/whip/src/Request/RequestAdapter.php +0 -47
  34. includes/vendor/vectorface/whip/src/Request/SuperglobalRequestAdapter.php +0 -88
  35. includes/vendor/vectorface/whip/src/Whip.php +0 -285
  36. includes/vendor/vectorface/whip/tests/WhipTest.php +0 -459
  37. includes/vendor/vectorface/whip/tests/bootstrap.php +0 -3
  38. older-changelog.txt +0 -888
  39. readme.txt +9 -1
  40. wp-statistics.php +1 -1
assets/images/welcome/topcountry-widget.png DELETED
Binary file
assets/images/welcome/topreferring-widget.png DELETED
Binary file
assets/images/welcome/usersonline-widget.png DELETED
Binary file
assets/images/welcome/visitor-ip.png ADDED
Binary file
assets/js/moment.min.js ADDED
@@ -0,0 +1 @@
 
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.moment=t()}(this,function(){"use strict";var e,i;function c(){return e.apply(null,arguments)}function o(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function u(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function l(e){return void 0===e}function h(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function d(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function f(e,t){var n,s=[];for(n=0;n<e.length;++n)s.push(t(e[n],n));return s}function m(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function _(e,t){for(var n in t)m(t,n)&&(e[n]=t[n]);return m(t,"toString")&&(e.toString=t.toString),m(t,"valueOf")&&(e.valueOf=t.valueOf),e}function y(e,t,n,s){return Tt(e,t,n,s,!0).utc()}function g(e){return null==e._pf&&(e._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],meridiem:null,rfc2822:!1,weekdayMismatch:!1}),e._pf}function v(e){if(null==e._isValid){var t=g(e),n=i.call(t.parsedDateParts,function(e){return null!=e}),s=!isNaN(e._d.getTime())&&t.overflow<0&&!t.empty&&!t.invalidMonth&&!t.invalidWeekday&&!t.weekdayMismatch&&!t.nullInput&&!t.invalidFormat&&!t.userInvalidated&&(!t.meridiem||t.meridiem&&n);if(e._strict&&(s=s&&0===t.charsLeftOver&&0===t.unusedTokens.length&&void 0===t.bigHour),null!=Object.isFrozen&&Object.isFrozen(e))return s;e._isValid=s}return e._isValid}function p(e){var t=y(NaN);return null!=e?_(g(t),e):g(t).userInvalidated=!0,t}i=Array.prototype.some?Array.prototype.some:function(e){for(var t=Object(this),n=t.length>>>0,s=0;s<n;s++)if(s in t&&e.call(this,t[s],s,t))return!0;return!1};var r=c.momentProperties=[];function w(e,t){var n,s,i;if(l(t._isAMomentObject)||(e._isAMomentObject=t._isAMomentObject),l(t._i)||(e._i=t._i),l(t._f)||(e._f=t._f),l(t._l)||(e._l=t._l),l(t._strict)||(e._strict=t._strict),l(t._tzm)||(e._tzm=t._tzm),l(t._isUTC)||(e._isUTC=t._isUTC),l(t._offset)||(e._offset=t._offset),l(t._pf)||(e._pf=g(t)),l(t._locale)||(e._locale=t._locale),0<r.length)for(n=0;n<r.length;n++)l(i=t[s=r[n]])||(e[s]=i);return e}var t=!1;function M(e){w(this,e),this._d=new Date(null!=e._d?e._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),!1===t&&(t=!0,c.updateOffset(this),t=!1)}function k(e){return e instanceof M||null!=e&&null!=e._isAMomentObject}function S(e){return e<0?Math.ceil(e)||0:Math.floor(e)}function D(e){var t=+e,n=0;return 0!==t&&isFinite(t)&&(n=S(t)),n}function a(e,t,n){var s,i=Math.min(e.length,t.length),r=Math.abs(e.length-t.length),a=0;for(s=0;s<i;s++)(n&&e[s]!==t[s]||!n&&D(e[s])!==D(t[s]))&&a++;return a+r}function Y(e){!1===c.suppressDeprecationWarnings&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+e)}function n(i,r){var a=!0;return _(function(){if(null!=c.deprecationHandler&&c.deprecationHandler(null,i),a){for(var e,t=[],n=0;n<arguments.length;n++){if(e="","object"==typeof arguments[n]){for(var s in e+="\n["+n+"] ",arguments[0])e+=s+": "+arguments[0][s]+", ";e=e.slice(0,-2)}else e=arguments[n];t.push(e)}Y(i+"\nArguments: "+Array.prototype.slice.call(t).join("")+"\n"+(new Error).stack),a=!1}return r.apply(this,arguments)},r)}var s,O={};function T(e,t){null!=c.deprecationHandler&&c.deprecationHandler(e,t),O[e]||(Y(t),O[e]=!0)}function b(e){return e instanceof Function||"[object Function]"===Object.prototype.toString.call(e)}function x(e,t){var n,s=_({},e);for(n in t)m(t,n)&&(u(e[n])&&u(t[n])?(s[n]={},_(s[n],e[n]),_(s[n],t[n])):null!=t[n]?s[n]=t[n]:delete s[n]);for(n in e)m(e,n)&&!m(t,n)&&u(e[n])&&(s[n]=_({},s[n]));return s}function P(e){null!=e&&this.set(e)}c.suppressDeprecationWarnings=!1,c.deprecationHandler=null,s=Object.keys?Object.keys:function(e){var t,n=[];for(t in e)m(e,t)&&n.push(t);return n};var W={};function C(e,t){var n=e.toLowerCase();W[n]=W[n+"s"]=W[t]=e}function H(e){return"string"==typeof e?W[e]||W[e.toLowerCase()]:void 0}function R(e){var t,n,s={};for(n in e)m(e,n)&&(t=H(n))&&(s[t]=e[n]);return s}var U={};function F(e,t){U[e]=t}function L(e,t,n){var s=""+Math.abs(e),i=t-s.length;return(0<=e?n?"+":"":"-")+Math.pow(10,Math.max(0,i)).toString().substr(1)+s}var N=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,G=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,V={},E={};function I(e,t,n,s){var i=s;"string"==typeof s&&(i=function(){return this[s]()}),e&&(E[e]=i),t&&(E[t[0]]=function(){return L(i.apply(this,arguments),t[1],t[2])}),n&&(E[n]=function(){return this.localeData().ordinal(i.apply(this,arguments),e)})}function A(e,t){return e.isValid()?(t=j(t,e.localeData()),V[t]=V[t]||function(s){var e,i,t,r=s.match(N);for(e=0,i=r.length;e<i;e++)E[r[e]]?r[e]=E[r[e]]:r[e]=(t=r[e]).match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"");return function(e){var t,n="";for(t=0;t<i;t++)n+=b(r[t])?r[t].call(e,s):r[t];return n}}(t),V[t](e)):e.localeData().invalidDate()}function j(e,t){var n=5;function s(e){return t.longDateFormat(e)||e}for(G.lastIndex=0;0<=n&&G.test(e);)e=e.replace(G,s),G.lastIndex=0,n-=1;return e}var Z=/\d/,z=/\d\d/,$=/\d{3}/,q=/\d{4}/,J=/[+-]?\d{6}/,B=/\d\d?/,Q=/\d\d\d\d?/,X=/\d\d\d\d\d\d?/,K=/\d{1,3}/,ee=/\d{1,4}/,te=/[+-]?\d{1,6}/,ne=/\d+/,se=/[+-]?\d+/,ie=/Z|[+-]\d\d:?\d\d/gi,re=/Z|[+-]\d\d(?::?\d\d)?/gi,ae=/[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i,oe={};function ue(e,n,s){oe[e]=b(n)?n:function(e,t){return e&&s?s:n}}function le(e,t){return m(oe,e)?oe[e](t._strict,t._locale):new RegExp(he(e.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(e,t,n,s,i){return t||n||s||i})))}function he(e){return e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}var de={};function ce(e,n){var t,s=n;for("string"==typeof e&&(e=[e]),h(n)&&(s=function(e,t){t[n]=D(e)}),t=0;t<e.length;t++)de[e[t]]=s}function fe(e,i){ce(e,function(e,t,n,s){n._w=n._w||{},i(e,n._w,n,s)})}var me=0,_e=1,ye=2,ge=3,ve=4,pe=5,we=6,Me=7,ke=8;function Se(e){return De(e)?366:365}function De(e){return e%4==0&&e%100!=0||e%400==0}I("Y",0,0,function(){var e=this.year();return e<=9999?""+e:"+"+e}),I(0,["YY",2],0,function(){return this.year()%100}),I(0,["YYYY",4],0,"year"),I(0,["YYYYY",5],0,"year"),I(0,["YYYYYY",6,!0],0,"year"),C("year","y"),F("year",1),ue("Y",se),ue("YY",B,z),ue("YYYY",ee,q),ue("YYYYY",te,J),ue("YYYYYY",te,J),ce(["YYYYY","YYYYYY"],me),ce("YYYY",function(e,t){t[me]=2===e.length?c.parseTwoDigitYear(e):D(e)}),ce("YY",function(e,t){t[me]=c.parseTwoDigitYear(e)}),ce("Y",function(e,t){t[me]=parseInt(e,10)}),c.parseTwoDigitYear=function(e){return D(e)+(68<D(e)?1900:2e3)};var Ye,Oe=Te("FullYear",!0);function Te(t,n){return function(e){return null!=e?(xe(this,t,e),c.updateOffset(this,n),this):be(this,t)}}function be(e,t){return e.isValid()?e._d["get"+(e._isUTC?"UTC":"")+t]():NaN}function xe(e,t,n){e.isValid()&&!isNaN(n)&&("FullYear"===t&&De(e.year())&&1===e.month()&&29===e.date()?e._d["set"+(e._isUTC?"UTC":"")+t](n,e.month(),Pe(n,e.month())):e._d["set"+(e._isUTC?"UTC":"")+t](n))}function Pe(e,t){if(isNaN(e)||isNaN(t))return NaN;var n,s=(t%(n=12)+n)%n;return e+=(t-s)/12,1===s?De(e)?29:28:31-s%7%2}Ye=Array.prototype.indexOf?Array.prototype.indexOf:function(e){var t;for(t=0;t<this.length;++t)if(this[t]===e)return t;return-1},I("M",["MM",2],"Mo",function(){return this.month()+1}),I("MMM",0,0,function(e){return this.localeData().monthsShort(this,e)}),I("MMMM",0,0,function(e){return this.localeData().months(this,e)}),C("month","M"),F("month",8),ue("M",B),ue("MM",B,z),ue("MMM",function(e,t){return t.monthsShortRegex(e)}),ue("MMMM",function(e,t){return t.monthsRegex(e)}),ce(["M","MM"],function(e,t){t[_e]=D(e)-1}),ce(["MMM","MMMM"],function(e,t,n,s){var i=n._locale.monthsParse(e,s,n._strict);null!=i?t[_e]=i:g(n).invalidMonth=e});var We=/D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/,Ce="January_February_March_April_May_June_July_August_September_October_November_December".split("_");var He="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_");function Re(e,t){var n;if(!e.isValid())return e;if("string"==typeof t)if(/^\d+$/.test(t))t=D(t);else if(!h(t=e.localeData().monthsParse(t)))return e;return n=Math.min(e.date(),Pe(e.year(),t)),e._d["set"+(e._isUTC?"UTC":"")+"Month"](t,n),e}function Ue(e){return null!=e?(Re(this,e),c.updateOffset(this,!0),this):be(this,"Month")}var Fe=ae;var Le=ae;function Ne(){function e(e,t){return t.length-e.length}var t,n,s=[],i=[],r=[];for(t=0;t<12;t++)n=y([2e3,t]),s.push(this.monthsShort(n,"")),i.push(this.months(n,"")),r.push(this.months(n,"")),r.push(this.monthsShort(n,""));for(s.sort(e),i.sort(e),r.sort(e),t=0;t<12;t++)s[t]=he(s[t]),i[t]=he(i[t]);for(t=0;t<24;t++)r[t]=he(r[t]);this._monthsRegex=new RegExp("^("+r.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+i.join("|")+")","i"),this._monthsShortStrictRegex=new RegExp("^("+s.join("|")+")","i")}function Ge(e){var t;if(e<100&&0<=e){var n=Array.prototype.slice.call(arguments);n[0]=e+400,t=new Date(Date.UTC.apply(null,n)),isFinite(t.getUTCFullYear())&&t.setUTCFullYear(e)}else t=new Date(Date.UTC.apply(null,arguments));return t}function Ve(e,t,n){var s=7+t-n;return-((7+Ge(e,0,s).getUTCDay()-t)%7)+s-1}function Ee(e,t,n,s,i){var r,a,o=1+7*(t-1)+(7+n-s)%7+Ve(e,s,i);return a=o<=0?Se(r=e-1)+o:o>Se(e)?(r=e+1,o-Se(e)):(r=e,o),{year:r,dayOfYear:a}}function Ie(e,t,n){var s,i,r=Ve(e.year(),t,n),a=Math.floor((e.dayOfYear()-r-1)/7)+1;return a<1?s=a+Ae(i=e.year()-1,t,n):a>Ae(e.year(),t,n)?(s=a-Ae(e.year(),t,n),i=e.year()+1):(i=e.year(),s=a),{week:s,year:i}}function Ae(e,t,n){var s=Ve(e,t,n),i=Ve(e+1,t,n);return(Se(e)-s+i)/7}I("w",["ww",2],"wo","week"),I("W",["WW",2],"Wo","isoWeek"),C("week","w"),C("isoWeek","W"),F("week",5),F("isoWeek",5),ue("w",B),ue("ww",B,z),ue("W",B),ue("WW",B,z),fe(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=D(e)});function je(e,t){return e.slice(t,7).concat(e.slice(0,t))}I("d",0,"do","day"),I("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),I("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),I("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),I("e",0,0,"weekday"),I("E",0,0,"isoWeekday"),C("day","d"),C("weekday","e"),C("isoWeekday","E"),F("day",11),F("weekday",11),F("isoWeekday",11),ue("d",B),ue("e",B),ue("E",B),ue("dd",function(e,t){return t.weekdaysMinRegex(e)}),ue("ddd",function(e,t){return t.weekdaysShortRegex(e)}),ue("dddd",function(e,t){return t.weekdaysRegex(e)}),fe(["dd","ddd","dddd"],function(e,t,n,s){var i=n._locale.weekdaysParse(e,s,n._strict);null!=i?t.d=i:g(n).invalidWeekday=e}),fe(["d","e","E"],function(e,t,n,s){t[s]=D(e)});var Ze="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_");var ze="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_");var $e="Su_Mo_Tu_We_Th_Fr_Sa".split("_");var qe=ae;var Je=ae;var Be=ae;function Qe(){function e(e,t){return t.length-e.length}var t,n,s,i,r,a=[],o=[],u=[],l=[];for(t=0;t<7;t++)n=y([2e3,1]).day(t),s=this.weekdaysMin(n,""),i=this.weekdaysShort(n,""),r=this.weekdays(n,""),a.push(s),o.push(i),u.push(r),l.push(s),l.push(i),l.push(r);for(a.sort(e),o.sort(e),u.sort(e),l.sort(e),t=0;t<7;t++)o[t]=he(o[t]),u[t]=he(u[t]),l[t]=he(l[t]);this._weekdaysRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+a.join("|")+")","i")}function Xe(){return this.hours()%12||12}function Ke(e,t){I(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function et(e,t){return t._meridiemParse}I("H",["HH",2],0,"hour"),I("h",["hh",2],0,Xe),I("k",["kk",2],0,function(){return this.hours()||24}),I("hmm",0,0,function(){return""+Xe.apply(this)+L(this.minutes(),2)}),I("hmmss",0,0,function(){return""+Xe.apply(this)+L(this.minutes(),2)+L(this.seconds(),2)}),I("Hmm",0,0,function(){return""+this.hours()+L(this.minutes(),2)}),I("Hmmss",0,0,function(){return""+this.hours()+L(this.minutes(),2)+L(this.seconds(),2)}),Ke("a",!0),Ke("A",!1),C("hour","h"),F("hour",13),ue("a",et),ue("A",et),ue("H",B),ue("h",B),ue("k",B),ue("HH",B,z),ue("hh",B,z),ue("kk",B,z),ue("hmm",Q),ue("hmmss",X),ue("Hmm",Q),ue("Hmmss",X),ce(["H","HH"],ge),ce(["k","kk"],function(e,t,n){var s=D(e);t[ge]=24===s?0:s}),ce(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),ce(["h","hh"],function(e,t,n){t[ge]=D(e),g(n).bigHour=!0}),ce("hmm",function(e,t,n){var s=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s)),g(n).bigHour=!0}),ce("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s,2)),t[pe]=D(e.substr(i)),g(n).bigHour=!0}),ce("Hmm",function(e,t,n){var s=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s))}),ce("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s,2)),t[pe]=D(e.substr(i))});var tt,nt=Te("Hours",!0),st={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ce,monthsShort:He,week:{dow:0,doy:6},weekdays:Ze,weekdaysMin:$e,weekdaysShort:ze,meridiemParse:/[ap]\.?m?\.?/i},it={},rt={};function at(e){return e?e.toLowerCase().replace("_","-"):e}function ot(e){var t=null;if(!it[e]&&"undefined"!=typeof module&&module&&module.exports)try{t=tt._abbr,require("./locale/"+e),ut(t)}catch(e){}return it[e]}function ut(e,t){var n;return e&&((n=l(t)?ht(e):lt(e,t))?tt=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),tt._abbr}function lt(e,t){if(null===t)return delete it[e],null;var n,s=st;if(t.abbr=e,null!=it[e])T("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=it[e]._config;else if(null!=t.parentLocale)if(null!=it[t.parentLocale])s=it[t.parentLocale]._config;else{if(null==(n=ot(t.parentLocale)))return rt[t.parentLocale]||(rt[t.parentLocale]=[]),rt[t.parentLocale].push({name:e,config:t}),null;s=n._config}return it[e]=new P(x(s,t)),rt[e]&&rt[e].forEach(function(e){lt(e.name,e.config)}),ut(e),it[e]}function ht(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return tt;if(!o(e)){if(t=ot(e))return t;e=[e]}return function(e){for(var t,n,s,i,r=0;r<e.length;){for(t=(i=at(e[r]).split("-")).length,n=(n=at(e[r+1]))?n.split("-"):null;0<t;){if(s=ot(i.slice(0,t).join("-")))return s;if(n&&n.length>=t&&a(i,n,!0)>=t-1)break;t--}r++}return tt}(e)}function dt(e){var t,n=e._a;return n&&-2===g(e).overflow&&(t=n[_e]<0||11<n[_e]?_e:n[ye]<1||n[ye]>Pe(n[me],n[_e])?ye:n[ge]<0||24<n[ge]||24===n[ge]&&(0!==n[ve]||0!==n[pe]||0!==n[we])?ge:n[ve]<0||59<n[ve]?ve:n[pe]<0||59<n[pe]?pe:n[we]<0||999<n[we]?we:-1,g(e)._overflowDayOfYear&&(t<me||ye<t)&&(t=ye),g(e)._overflowWeeks&&-1===t&&(t=Me),g(e)._overflowWeekday&&-1===t&&(t=ke),g(e).overflow=t),e}function ct(e,t,n){return null!=e?e:null!=t?t:n}function ft(e){var t,n,s,i,r,a=[];if(!e._d){var o,u;for(o=e,u=new Date(c.now()),s=o._useUTC?[u.getUTCFullYear(),u.getUTCMonth(),u.getUTCDate()]:[u.getFullYear(),u.getMonth(),u.getDate()],e._w&&null==e._a[ye]&&null==e._a[_e]&&function(e){var t,n,s,i,r,a,o,u;if(null!=(t=e._w).GG||null!=t.W||null!=t.E)r=1,a=4,n=ct(t.GG,e._a[me],Ie(bt(),1,4).year),s=ct(t.W,1),((i=ct(t.E,1))<1||7<i)&&(u=!0);else{r=e._locale._week.dow,a=e._locale._week.doy;var l=Ie(bt(),r,a);n=ct(t.gg,e._a[me],l.year),s=ct(t.w,l.week),null!=t.d?((i=t.d)<0||6<i)&&(u=!0):null!=t.e?(i=t.e+r,(t.e<0||6<t.e)&&(u=!0)):i=r}s<1||s>Ae(n,r,a)?g(e)._overflowWeeks=!0:null!=u?g(e)._overflowWeekday=!0:(o=Ee(n,s,i,r,a),e._a[me]=o.year,e._dayOfYear=o.dayOfYear)}(e),null!=e._dayOfYear&&(r=ct(e._a[me],s[me]),(e._dayOfYear>Se(r)||0===e._dayOfYear)&&(g(e)._overflowDayOfYear=!0),n=Ge(r,0,e._dayOfYear),e._a[_e]=n.getUTCMonth(),e._a[ye]=n.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=a[t]=s[t];for(;t<7;t++)e._a[t]=a[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[ge]&&0===e._a[ve]&&0===e._a[pe]&&0===e._a[we]&&(e._nextDay=!0,e._a[ge]=0),e._d=(e._useUTC?Ge:function(e,t,n,s,i,r,a){var o;return e<100&&0<=e?(o=new Date(e+400,t,n,s,i,r,a),isFinite(o.getFullYear())&&o.setFullYear(e)):o=new Date(e,t,n,s,i,r,a),o}).apply(null,a),i=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[ge]=24),e._w&&void 0!==e._w.d&&e._w.d!==i&&(g(e).weekdayMismatch=!0)}}var mt=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,_t=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,yt=/Z|[+-]\d\d(?::?\d\d)?/,gt=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],vt=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],pt=/^\/?Date\((\-?\d+)/i;function wt(e){var t,n,s,i,r,a,o=e._i,u=mt.exec(o)||_t.exec(o);if(u){for(g(e).iso=!0,t=0,n=gt.length;t<n;t++)if(gt[t][1].exec(u[1])){i=gt[t][0],s=!1!==gt[t][2];break}if(null==i)return void(e._isValid=!1);if(u[3]){for(t=0,n=vt.length;t<n;t++)if(vt[t][1].exec(u[3])){r=(u[2]||" ")+vt[t][0];break}if(null==r)return void(e._isValid=!1)}if(!s&&null!=r)return void(e._isValid=!1);if(u[4]){if(!yt.exec(u[4]))return void(e._isValid=!1);a="Z"}e._f=i+(r||"")+(a||""),Yt(e)}else e._isValid=!1}var Mt=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/;function kt(e,t,n,s,i,r){var a=[function(e){var t=parseInt(e,10);{if(t<=49)return 2e3+t;if(t<=999)return 1900+t}return t}(e),He.indexOf(t),parseInt(n,10),parseInt(s,10),parseInt(i,10)];return r&&a.push(parseInt(r,10)),a}var St={UT:0,GMT:0,EDT:-240,EST:-300,CDT:-300,CST:-360,MDT:-360,MST:-420,PDT:-420,PST:-480};function Dt(e){var t,n,s,i=Mt.exec(e._i.replace(/\([^)]*\)|[\n\t]/g," ").replace(/(\s\s+)/g," ").replace(/^\s\s*/,"").replace(/\s\s*$/,""));if(i){var r=kt(i[4],i[3],i[2],i[5],i[6],i[7]);if(t=i[1],n=r,s=e,t&&ze.indexOf(t)!==new Date(n[0],n[1],n[2]).getDay()&&(g(s).weekdayMismatch=!0,!(s._isValid=!1)))return;e._a=r,e._tzm=function(e,t,n){if(e)return St[e];if(t)return 0;var s=parseInt(n,10),i=s%100;return(s-i)/100*60+i}(i[8],i[9],i[10]),e._d=Ge.apply(null,e._a),e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),g(e).rfc2822=!0}else e._isValid=!1}function Yt(e){if(e._f!==c.ISO_8601)if(e._f!==c.RFC_2822){e._a=[],g(e).empty=!0;var t,n,s,i,r,a,o,u,l=""+e._i,h=l.length,d=0;for(s=j(e._f,e._locale).match(N)||[],t=0;t<s.length;t++)i=s[t],(n=(l.match(le(i,e))||[])[0])&&(0<(r=l.substr(0,l.indexOf(n))).length&&g(e).unusedInput.push(r),l=l.slice(l.indexOf(n)+n.length),d+=n.length),E[i]?(n?g(e).empty=!1:g(e).unusedTokens.push(i),a=i,u=e,null!=(o=n)&&m(de,a)&&de[a](o,u._a,u,a)):e._strict&&!n&&g(e).unusedTokens.push(i);g(e).charsLeftOver=h-d,0<l.length&&g(e).unusedInput.push(l),e._a[ge]<=12&&!0===g(e).bigHour&&0<e._a[ge]&&(g(e).bigHour=void 0),g(e).parsedDateParts=e._a.slice(0),g(e).meridiem=e._meridiem,e._a[ge]=function(e,t,n){var s;if(null==n)return t;return null!=e.meridiemHour?e.meridiemHour(t,n):(null!=e.isPM&&((s=e.isPM(n))&&t<12&&(t+=12),s||12!==t||(t=0)),t)}(e._locale,e._a[ge],e._meridiem),ft(e),dt(e)}else Dt(e);else wt(e)}function Ot(e){var t,n,s,i,r=e._i,a=e._f;return e._locale=e._locale||ht(e._l),null===r||void 0===a&&""===r?p({nullInput:!0}):("string"==typeof r&&(e._i=r=e._locale.preparse(r)),k(r)?new M(dt(r)):(d(r)?e._d=r:o(a)?function(e){var t,n,s,i,r;if(0===e._f.length)return g(e).invalidFormat=!0,e._d=new Date(NaN);for(i=0;i<e._f.length;i++)r=0,t=w({},e),null!=e._useUTC&&(t._useUTC=e._useUTC),t._f=e._f[i],Yt(t),v(t)&&(r+=g(t).charsLeftOver,r+=10*g(t).unusedTokens.length,g(t).score=r,(null==s||r<s)&&(s=r,n=t));_(e,n||t)}(e):a?Yt(e):l(n=(t=e)._i)?t._d=new Date(c.now()):d(n)?t._d=new Date(n.valueOf()):"string"==typeof n?(s=t,null===(i=pt.exec(s._i))?(wt(s),!1===s._isValid&&(delete s._isValid,Dt(s),!1===s._isValid&&(delete s._isValid,c.createFromInputFallback(s)))):s._d=new Date(+i[1])):o(n)?(t._a=f(n.slice(0),function(e){return parseInt(e,10)}),ft(t)):u(n)?function(e){if(!e._d){var t=R(e._i);e._a=f([t.year,t.month,t.day||t.date,t.hour,t.minute,t.second,t.millisecond],function(e){return e&&parseInt(e,10)}),ft(e)}}(t):h(n)?t._d=new Date(n):c.createFromInputFallback(t),v(e)||(e._d=null),e))}function Tt(e,t,n,s,i){var r,a={};return!0!==n&&!1!==n||(s=n,n=void 0),(u(e)&&function(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;var t;for(t in e)if(e.hasOwnProperty(t))return!1;return!0}(e)||o(e)&&0===e.length)&&(e=void 0),a._isAMomentObject=!0,a._useUTC=a._isUTC=i,a._l=n,a._i=e,a._f=t,a._strict=s,(r=new M(dt(Ot(a))))._nextDay&&(r.add(1,"d"),r._nextDay=void 0),r}function bt(e,t,n,s){return Tt(e,t,n,s,!1)}c.createFromInputFallback=n("value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",function(e){e._d=new Date(e._i+(e._useUTC?" UTC":""))}),c.ISO_8601=function(){},c.RFC_2822=function(){};var xt=n("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var e=bt.apply(null,arguments);return this.isValid()&&e.isValid()?e<this?this:e:p()}),Pt=n("moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var e=bt.apply(null,arguments);return this.isValid()&&e.isValid()?this<e?this:e:p()});function Wt(e,t){var n,s;if(1===t.length&&o(t[0])&&(t=t[0]),!t.length)return bt();for(n=t[0],s=1;s<t.length;++s)t[s].isValid()&&!t[s][e](n)||(n=t[s]);return n}var Ct=["year","quarter","month","week","day","hour","minute","second","millisecond"];function Ht(e){var t=R(e),n=t.year||0,s=t.quarter||0,i=t.month||0,r=t.week||t.isoWeek||0,a=t.day||0,o=t.hour||0,u=t.minute||0,l=t.second||0,h=t.millisecond||0;this._isValid=function(e){for(var t in e)if(-1===Ye.call(Ct,t)||null!=e[t]&&isNaN(e[t]))return!1;for(var n=!1,s=0;s<Ct.length;++s)if(e[Ct[s]]){if(n)return!1;parseFloat(e[Ct[s]])!==D(e[Ct[s]])&&(n=!0)}return!0}(t),this._milliseconds=+h+1e3*l+6e4*u+1e3*o*60*60,this._days=+a+7*r,this._months=+i+3*s+12*n,this._data={},this._locale=ht(),this._bubble()}function Rt(e){return e instanceof Ht}function Ut(e){return e<0?-1*Math.round(-1*e):Math.round(e)}function Ft(e,n){I(e,0,0,function(){var e=this.utcOffset(),t="+";return e<0&&(e=-e,t="-"),t+L(~~(e/60),2)+n+L(~~e%60,2)})}Ft("Z",":"),Ft("ZZ",""),ue("Z",re),ue("ZZ",re),ce(["Z","ZZ"],function(e,t,n){n._useUTC=!0,n._tzm=Nt(re,e)});var Lt=/([\+\-]|\d\d)/gi;function Nt(e,t){var n=(t||"").match(e);if(null===n)return null;var s=((n[n.length-1]||[])+"").match(Lt)||["-",0,0],i=60*s[1]+D(s[2]);return 0===i?0:"+"===s[0]?i:-i}function Gt(e,t){var n,s;return t._isUTC?(n=t.clone(),s=(k(e)||d(e)?e.valueOf():bt(e).valueOf())-n.valueOf(),n._d.setTime(n._d.valueOf()+s),c.updateOffset(n,!1),n):bt(e).local()}function Vt(e){return 15*-Math.round(e._d.getTimezoneOffset()/15)}function Et(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}c.updateOffset=function(){};var It=/^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,At=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;function jt(e,t){var n,s,i,r=e,a=null;return Rt(e)?r={ms:e._milliseconds,d:e._days,M:e._months}:h(e)?(r={},t?r[t]=e:r.milliseconds=e):(a=It.exec(e))?(n="-"===a[1]?-1:1,r={y:0,d:D(a[ye])*n,h:D(a[ge])*n,m:D(a[ve])*n,s:D(a[pe])*n,ms:D(Ut(1e3*a[we]))*n}):(a=At.exec(e))?(n="-"===a[1]?-1:1,r={y:Zt(a[2],n),M:Zt(a[3],n),w:Zt(a[4],n),d:Zt(a[5],n),h:Zt(a[6],n),m:Zt(a[7],n),s:Zt(a[8],n)}):null==r?r={}:"object"==typeof r&&("from"in r||"to"in r)&&(i=function(e,t){var n;if(!e.isValid()||!t.isValid())return{milliseconds:0,months:0};t=Gt(t,e),e.isBefore(t)?n=zt(e,t):((n=zt(t,e)).milliseconds=-n.milliseconds,n.months=-n.months);return n}(bt(r.from),bt(r.to)),(r={}).ms=i.milliseconds,r.M=i.months),s=new Ht(r),Rt(e)&&m(e,"_locale")&&(s._locale=e._locale),s}function Zt(e,t){var n=e&&parseFloat(e.replace(",","."));return(isNaN(n)?0:n)*t}function zt(e,t){var n={};return n.months=t.month()-e.month()+12*(t.year()-e.year()),e.clone().add(n.months,"M").isAfter(t)&&--n.months,n.milliseconds=+t-+e.clone().add(n.months,"M"),n}function $t(s,i){return function(e,t){var n;return null===t||isNaN(+t)||(T(i,"moment()."+i+"(period, number) is deprecated. Please use moment()."+i+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),n=e,e=t,t=n),qt(this,jt(e="string"==typeof e?+e:e,t),s),this}}function qt(e,t,n,s){var i=t._milliseconds,r=Ut(t._days),a=Ut(t._months);e.isValid()&&(s=null==s||s,a&&Re(e,be(e,"Month")+a*n),r&&xe(e,"Date",be(e,"Date")+r*n),i&&e._d.setTime(e._d.valueOf()+i*n),s&&c.updateOffset(e,r||a))}jt.fn=Ht.prototype,jt.invalid=function(){return jt(NaN)};var Jt=$t(1,"add"),Bt=$t(-1,"subtract");function Qt(e,t){var n=12*(t.year()-e.year())+(t.month()-e.month()),s=e.clone().add(n,"months");return-(n+(t-s<0?(t-s)/(s-e.clone().add(n-1,"months")):(t-s)/(e.clone().add(n+1,"months")-s)))||0}function Xt(e){var t;return void 0===e?this._locale._abbr:(null!=(t=ht(e))&&(this._locale=t),this)}c.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",c.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var Kt=n("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(e){return void 0===e?this.localeData():this.locale(e)});function en(){return this._locale}var tn=126227808e5;function nn(e,t){return(e%t+t)%t}function sn(e,t,n){return e<100&&0<=e?new Date(e+400,t,n)-tn:new Date(e,t,n).valueOf()}function rn(e,t,n){return e<100&&0<=e?Date.UTC(e+400,t,n)-tn:Date.UTC(e,t,n)}function an(e,t){I(0,[e,e.length],0,t)}function on(e,t,n,s,i){var r;return null==e?Ie(this,s,i).year:((r=Ae(e,s,i))<t&&(t=r),function(e,t,n,s,i){var r=Ee(e,t,n,s,i),a=Ge(r.year,0,r.dayOfYear);return this.year(a.getUTCFullYear()),this.month(a.getUTCMonth()),this.date(a.getUTCDate()),this}.call(this,e,t,n,s,i))}I(0,["gg",2],0,function(){return this.weekYear()%100}),I(0,["GG",2],0,function(){return this.isoWeekYear()%100}),an("gggg","weekYear"),an("ggggg","weekYear"),an("GGGG","isoWeekYear"),an("GGGGG","isoWeekYear"),C("weekYear","gg"),C("isoWeekYear","GG"),F("weekYear",1),F("isoWeekYear",1),ue("G",se),ue("g",se),ue("GG",B,z),ue("gg",B,z),ue("GGGG",ee,q),ue("gggg",ee,q),ue("GGGGG",te,J),ue("ggggg",te,J),fe(["gggg","ggggg","GGGG","GGGGG"],function(e,t,n,s){t[s.substr(0,2)]=D(e)}),fe(["gg","GG"],function(e,t,n,s){t[s]=c.parseTwoDigitYear(e)}),I("Q",0,"Qo","quarter"),C("quarter","Q"),F("quarter",7),ue("Q",Z),ce("Q",function(e,t){t[_e]=3*(D(e)-1)}),I("D",["DD",2],"Do","date"),C("date","D"),F("date",9),ue("D",B),ue("DD",B,z),ue("Do",function(e,t){return e?t._dayOfMonthOrdinalParse||t._ordinalParse:t._dayOfMonthOrdinalParseLenient}),ce(["D","DD"],ye),ce("Do",function(e,t){t[ye]=D(e.match(B)[0])});var un=Te("Date",!0);I("DDD",["DDDD",3],"DDDo","dayOfYear"),C("dayOfYear","DDD"),F("dayOfYear",4),ue("DDD",K),ue("DDDD",$),ce(["DDD","DDDD"],function(e,t,n){n._dayOfYear=D(e)}),I("m",["mm",2],0,"minute"),C("minute","m"),F("minute",14),ue("m",B),ue("mm",B,z),ce(["m","mm"],ve);var ln=Te("Minutes",!1);I("s",["ss",2],0,"second"),C("second","s"),F("second",15),ue("s",B),ue("ss",B,z),ce(["s","ss"],pe);var hn,dn=Te("Seconds",!1);for(I("S",0,0,function(){return~~(this.millisecond()/100)}),I(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),I(0,["SSS",3],0,"millisecond"),I(0,["SSSS",4],0,function(){return 10*this.millisecond()}),I(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),I(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),I(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),I(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),I(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),C("millisecond","ms"),F("millisecond",16),ue("S",K,Z),ue("SS",K,z),ue("SSS",K,$),hn="SSSS";hn.length<=9;hn+="S")ue(hn,ne);function cn(e,t){t[we]=D(1e3*("0."+e))}for(hn="S";hn.length<=9;hn+="S")ce(hn,cn);var fn=Te("Milliseconds",!1);I("z",0,0,"zoneAbbr"),I("zz",0,0,"zoneName");var mn=M.prototype;function _n(e){return e}mn.add=Jt,mn.calendar=function(e,t){var n=e||bt(),s=Gt(n,this).startOf("day"),i=c.calendarFormat(this,s)||"sameElse",r=t&&(b(t[i])?t[i].call(this,n):t[i]);return this.format(r||this.localeData().calendar(i,this,bt(n)))},mn.clone=function(){return new M(this)},mn.diff=function(e,t,n){var s,i,r;if(!this.isValid())return NaN;if(!(s=Gt(e,this)).isValid())return NaN;switch(i=6e4*(s.utcOffset()-this.utcOffset()),t=H(t)){case"year":r=Qt(this,s)/12;break;case"month":r=Qt(this,s);break;case"quarter":r=Qt(this,s)/3;break;case"second":r=(this-s)/1e3;break;case"minute":r=(this-s)/6e4;break;case"hour":r=(this-s)/36e5;break;case"day":r=(this-s-i)/864e5;break;case"week":r=(this-s-i)/6048e5;break;default:r=this-s}return n?r:S(r)},mn.endOf=function(e){var t;if(void 0===(e=H(e))||"millisecond"===e||!this.isValid())return this;var n=this._isUTC?rn:sn;switch(e){case"year":t=n(this.year()+1,0,1)-1;break;case"quarter":t=n(this.year(),this.month()-this.month()%3+3,1)-1;break;case"month":t=n(this.year(),this.month()+1,1)-1;break;case"week":t=n(this.year(),this.month(),this.date()-this.weekday()+7)-1;break;case"isoWeek":t=n(this.year(),this.month(),this.date()-(this.isoWeekday()-1)+7)-1;break;case"day":case"date":t=n(this.year(),this.month(),this.date()+1)-1;break;case"hour":t=this._d.valueOf(),t+=36e5-nn(t+(this._isUTC?0:6e4*this.utcOffset()),36e5)-1;break;case"minute":t=this._d.valueOf(),t+=6e4-nn(t,6e4)-1;break;case"second":t=this._d.valueOf(),t+=1e3-nn(t,1e3)-1;break}return this._d.setTime(t),c.updateOffset(this,!0),this},mn.format=function(e){e||(e=this.isUtc()?c.defaultFormatUtc:c.defaultFormat);var t=A(this,e);return this.localeData().postformat(t)},mn.from=function(e,t){return this.isValid()&&(k(e)&&e.isValid()||bt(e).isValid())?jt({to:this,from:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()},mn.fromNow=function(e){return this.from(bt(),e)},mn.to=function(e,t){return this.isValid()&&(k(e)&&e.isValid()||bt(e).isValid())?jt({from:this,to:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()},mn.toNow=function(e){return this.to(bt(),e)},mn.get=function(e){return b(this[e=H(e)])?this[e]():this},mn.invalidAt=function(){return g(this).overflow},mn.isAfter=function(e,t){var n=k(e)?e:bt(e);return!(!this.isValid()||!n.isValid())&&("millisecond"===(t=H(t)||"millisecond")?this.valueOf()>n.valueOf():n.valueOf()<this.clone().startOf(t).valueOf())},mn.isBefore=function(e,t){var n=k(e)?e:bt(e);return!(!this.isValid()||!n.isValid())&&("millisecond"===(t=H(t)||"millisecond")?this.valueOf()<n.valueOf():this.clone().endOf(t).valueOf()<n.valueOf())},mn.isBetween=function(e,t,n,s){var i=k(e)?e:bt(e),r=k(t)?t:bt(t);return!!(this.isValid()&&i.isValid()&&r.isValid())&&("("===(s=s||"()")[0]?this.isAfter(i,n):!this.isBefore(i,n))&&(")"===s[1]?this.isBefore(r,n):!this.isAfter(r,n))},mn.isSame=function(e,t){var n,s=k(e)?e:bt(e);return!(!this.isValid()||!s.isValid())&&("millisecond"===(t=H(t)||"millisecond")?this.valueOf()===s.valueOf():(n=s.valueOf(),this.clone().startOf(t).valueOf()<=n&&n<=this.clone().endOf(t).valueOf()))},mn.isSameOrAfter=function(e,t){return this.isSame(e,t)||this.isAfter(e,t)},mn.isSameOrBefore=function(e,t){return this.isSame(e,t)||this.isBefore(e,t)},mn.isValid=function(){return v(this)},mn.lang=Kt,mn.locale=Xt,mn.localeData=en,mn.max=Pt,mn.min=xt,mn.parsingFlags=function(){return _({},g(this))},mn.set=function(e,t){if("object"==typeof e)for(var n=function(e){var t=[];for(var n in e)t.push({unit:n,priority:U[n]});return t.sort(function(e,t){return e.priority-t.priority}),t}(e=R(e)),s=0;s<n.length;s++)this[n[s].unit](e[n[s].unit]);else if(b(this[e=H(e)]))return this[e](t);return this},mn.startOf=function(e){var t;if(void 0===(e=H(e))||"millisecond"===e||!this.isValid())return this;var n=this._isUTC?rn:sn;switch(e){case"year":t=n(this.year(),0,1);break;case"quarter":t=n(this.year(),this.month()-this.month()%3,1);break;case"month":t=n(this.year(),this.month(),1);break;case"week":t=n(this.year(),this.month(),this.date()-this.weekday());break;case"isoWeek":t=n(this.year(),this.month(),this.date()-(this.isoWeekday()-1));break;case"day":case"date":t=n(this.year(),this.month(),this.date());break;case"hour":t=this._d.valueOf(),t-=nn(t+(this._isUTC?0:6e4*this.utcOffset()),36e5);break;case"minute":t=this._d.valueOf(),t-=nn(t,6e4);break;case"second":t=this._d.valueOf(),t-=nn(t,1e3);break}return this._d.setTime(t),c.updateOffset(this,!0),this},mn.subtract=Bt,mn.toArray=function(){var e=this;return[e.year(),e.month(),e.date(),e.hour(),e.minute(),e.second(),e.millisecond()]},mn.toObject=function(){var e=this;return{years:e.year(),months:e.month(),date:e.date(),hours:e.hours(),minutes:e.minutes(),seconds:e.seconds(),milliseconds:e.milliseconds()}},mn.toDate=function(){return new Date(this.valueOf())},mn.toISOString=function(e){if(!this.isValid())return null;var t=!0!==e,n=t?this.clone().utc():this;return n.year()<0||9999<n.year()?A(n,t?"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ"):b(Date.prototype.toISOString)?t?this.toDate().toISOString():new Date(this.valueOf()+60*this.utcOffset()*1e3).toISOString().replace("Z",A(n,"Z")):A(n,t?"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYY-MM-DD[T]HH:mm:ss.SSSZ")},mn.inspect=function(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var e="moment",t="";this.isLocal()||(e=0===this.utcOffset()?"moment.utc":"moment.parseZone",t="Z");var n="["+e+'("]',s=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",i=t+'[")]';return this.format(n+s+"-MM-DD[T]HH:mm:ss.SSS"+i)},mn.toJSON=function(){return this.isValid()?this.toISOString():null},mn.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},mn.unix=function(){return Math.floor(this.valueOf()/1e3)},mn.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},mn.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},mn.year=Oe,mn.isLeapYear=function(){return De(this.year())},mn.weekYear=function(e){return on.call(this,e,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)},mn.isoWeekYear=function(e){return on.call(this,e,this.isoWeek(),this.isoWeekday(),1,4)},mn.quarter=mn.quarters=function(e){return null==e?Math.ceil((this.month()+1)/3):this.month(3*(e-1)+this.month()%3)},mn.month=Ue,mn.daysInMonth=function(){return Pe(this.year(),this.month())},mn.week=mn.weeks=function(e){var t=this.localeData().week(this);return null==e?t:this.add(7*(e-t),"d")},mn.isoWeek=mn.isoWeeks=function(e){var t=Ie(this,1,4).week;return null==e?t:this.add(7*(e-t),"d")},mn.weeksInYear=function(){var e=this.localeData()._week;return Ae(this.year(),e.dow,e.doy)},mn.isoWeeksInYear=function(){return Ae(this.year(),1,4)},mn.date=un,mn.day=mn.days=function(e){if(!this.isValid())return null!=e?this:NaN;var t,n,s=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=e?(t=e,n=this.localeData(),e="string"!=typeof t?t:isNaN(t)?"number"==typeof(t=n.weekdaysParse(t))?t:null:parseInt(t,10),this.add(e-s,"d")):s},mn.weekday=function(e){if(!this.isValid())return null!=e?this:NaN;var t=(this.day()+7-this.localeData()._week.dow)%7;return null==e?t:this.add(e-t,"d")},mn.isoWeekday=function(e){if(!this.isValid())return null!=e?this:NaN;if(null==e)return this.day()||7;var t,n,s=(t=e,n=this.localeData(),"string"==typeof t?n.weekdaysParse(t)%7||7:isNaN(t)?null:t);return this.day(this.day()%7?s:s-7)},mn.dayOfYear=function(e){var t=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==e?t:this.add(e-t,"d")},mn.hour=mn.hours=nt,mn.minute=mn.minutes=ln,mn.second=mn.seconds=dn,mn.millisecond=mn.milliseconds=fn,mn.utcOffset=function(e,t,n){var s,i=this._offset||0;if(!this.isValid())return null!=e?this:NaN;if(null==e)return this._isUTC?i:Vt(this);if("string"==typeof e){if(null===(e=Nt(re,e)))return this}else Math.abs(e)<16&&!n&&(e*=60);return!this._isUTC&&t&&(s=Vt(this)),this._offset=e,this._isUTC=!0,null!=s&&this.add(s,"m"),i!==e&&(!t||this._changeInProgress?qt(this,jt(e-i,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,c.updateOffset(this,!0),this._changeInProgress=null)),this},mn.utc=function(e){return this.utcOffset(0,e)},mn.local=function(e){return this._isUTC&&(this.utcOffset(0,e),this._isUTC=!1,e&&this.subtract(Vt(this),"m")),this},mn.parseZone=function(){if(null!=this._tzm)this.utcOffset(this._tzm,!1,!0);else if("string"==typeof this._i){var e=Nt(ie,this._i);null!=e?this.utcOffset(e):this.utcOffset(0,!0)}return this},mn.hasAlignedHourOffset=function(e){return!!this.isValid()&&(e=e?bt(e).utcOffset():0,(this.utcOffset()-e)%60==0)},mn.isDST=function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},mn.isLocal=function(){return!!this.isValid()&&!this._isUTC},mn.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},mn.isUtc=Et,mn.isUTC=Et,mn.zoneAbbr=function(){return this._isUTC?"UTC":""},mn.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},mn.dates=n("dates accessor is deprecated. Use date instead.",un),mn.months=n("months accessor is deprecated. Use month instead",Ue),mn.years=n("years accessor is deprecated. Use year instead",Oe),mn.zone=n("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()}),mn.isDSTShifted=n("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!l(this._isDSTShifted))return this._isDSTShifted;var e={};if(w(e,this),(e=Ot(e))._a){var t=e._isUTC?y(e._a):bt(e._a);this._isDSTShifted=this.isValid()&&0<a(e._a,t.toArray())}else this._isDSTShifted=!1;return this._isDSTShifted});var yn=P.prototype;function gn(e,t,n,s){var i=ht(),r=y().set(s,t);return i[n](r,e)}function vn(e,t,n){if(h(e)&&(t=e,e=void 0),e=e||"",null!=t)return gn(e,t,n,"month");var s,i=[];for(s=0;s<12;s++)i[s]=gn(e,s,n,"month");return i}function pn(e,t,n,s){t=("boolean"==typeof e?h(t)&&(n=t,t=void 0):(t=e,e=!1,h(n=t)&&(n=t,t=void 0)),t||"");var i,r=ht(),a=e?r._week.dow:0;if(null!=n)return gn(t,(n+a)%7,s,"day");var o=[];for(i=0;i<7;i++)o[i]=gn(t,(i+a)%7,s,"day");return o}yn.calendar=function(e,t,n){var s=this._calendar[e]||this._calendar.sameElse;return b(s)?s.call(t,n):s},yn.longDateFormat=function(e){var t=this._longDateFormat[e],n=this._longDateFormat[e.toUpperCase()];return t||!n?t:(this._longDateFormat[e]=n.replace(/MMMM|MM|DD|dddd/g,function(e){return e.slice(1)}),this._longDateFormat[e])},yn.invalidDate=function(){return this._invalidDate},yn.ordinal=function(e){return this._ordinal.replace("%d",e)},yn.preparse=_n,yn.postformat=_n,yn.relativeTime=function(e,t,n,s){var i=this._relativeTime[n];return b(i)?i(e,t,n,s):i.replace(/%d/i,e)},yn.pastFuture=function(e,t){var n=this._relativeTime[0<e?"future":"past"];return b(n)?n(t):n.replace(/%s/i,t)},yn.set=function(e){var t,n;for(n in e)b(t=e[n])?this[n]=t:this["_"+n]=t;this._config=e,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+"|"+/\d{1,2}/.source)},yn.months=function(e,t){return e?o(this._months)?this._months[e.month()]:this._months[(this._months.isFormat||We).test(t)?"format":"standalone"][e.month()]:o(this._months)?this._months:this._months.standalone},yn.monthsShort=function(e,t){return e?o(this._monthsShort)?this._monthsShort[e.month()]:this._monthsShort[We.test(t)?"format":"standalone"][e.month()]:o(this._monthsShort)?this._monthsShort:this._monthsShort.standalone},yn.monthsParse=function(e,t,n){var s,i,r;if(this._monthsParseExact)return function(e,t,n){var s,i,r,a=e.toLocaleLowerCase();if(!this._monthsParse)for(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[],s=0;s<12;++s)r=y([2e3,s]),this._shortMonthsParse[s]=this.monthsShort(r,"").toLocaleLowerCase(),this._longMonthsParse[s]=this.months(r,"").toLocaleLowerCase();return n?"MMM"===t?-1!==(i=Ye.call(this._shortMonthsParse,a))?i:null:-1!==(i=Ye.call(this._longMonthsParse,a))?i:null:"MMM"===t?-1!==(i=Ye.call(this._shortMonthsParse,a))?i:-1!==(i=Ye.call(this._longMonthsParse,a))?i:null:-1!==(i=Ye.call(this._longMonthsParse,a))?i:-1!==(i=Ye.call(this._shortMonthsParse,a))?i:null}.call(this,e,t,n);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),s=0;s<12;s++){if(i=y([2e3,s]),n&&!this._longMonthsParse[s]&&(this._longMonthsParse[s]=new RegExp("^"+this.months(i,"").replace(".","")+"$","i"),this._shortMonthsParse[s]=new RegExp("^"+this.monthsShort(i,"").replace(".","")+"$","i")),n||this._monthsParse[s]||(r="^"+this.months(i,"")+"|^"+this.monthsShort(i,""),this._monthsParse[s]=new RegExp(r.replace(".",""),"i")),n&&"MMMM"===t&&this._longMonthsParse[s].test(e))return s;if(n&&"MMM"===t&&this._shortMonthsParse[s].test(e))return s;if(!n&&this._monthsParse[s].test(e))return s}},yn.monthsRegex=function(e){return this._monthsParseExact?(m(this,"_monthsRegex")||Ne.call(this),e?this._monthsStrictRegex:this._monthsRegex):(m(this,"_monthsRegex")||(this._monthsRegex=Le),this._monthsStrictRegex&&e?this._monthsStrictRegex:this._monthsRegex)},yn.monthsShortRegex=function(e){return this._monthsParseExact?(m(this,"_monthsRegex")||Ne.call(this),e?this._monthsShortStrictRegex:this._monthsShortRegex):(m(this,"_monthsShortRegex")||(this._monthsShortRegex=Fe),this._monthsShortStrictRegex&&e?this._monthsShortStrictRegex:this._monthsShortRegex)},yn.week=function(e){return Ie(e,this._week.dow,this._week.doy).week},yn.firstDayOfYear=function(){return this._week.doy},yn.firstDayOfWeek=function(){return this._week.dow},yn.weekdays=function(e,t){var n=o(this._weekdays)?this._weekdays:this._weekdays[e&&!0!==e&&this._weekdays.isFormat.test(t)?"format":"standalone"];return!0===e?je(n,this._week.dow):e?n[e.day()]:n},yn.weekdaysMin=function(e){return!0===e?je(this._weekdaysMin,this._week.dow):e?this._weekdaysMin[e.day()]:this._weekdaysMin},yn.weekdaysShort=function(e){return!0===e?je(this._weekdaysShort,this._week.dow):e?this._weekdaysShort[e.day()]:this._weekdaysShort},yn.weekdaysParse=function(e,t,n){var s,i,r;if(this._weekdaysParseExact)return function(e,t,n){var s,i,r,a=e.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],s=0;s<7;++s)r=y([2e3,1]).day(s),this._minWeekdaysParse[s]=this.weekdaysMin(r,"").toLocaleLowerCase(),this._shortWeekdaysParse[s]=this.weekdaysShort(r,"").toLocaleLowerCase(),this._weekdaysParse[s]=this.weekdays(r,"").toLocaleLowerCase();return n?"dddd"===t?-1!==(i=Ye.call(this._weekdaysParse,a))?i:null:"ddd"===t?-1!==(i=Ye.call(this._shortWeekdaysParse,a))?i:null:-1!==(i=Ye.call(this._minWeekdaysParse,a))?i:null:"dddd"===t?-1!==(i=Ye.call(this._weekdaysParse,a))?i:-1!==(i=Ye.call(this._shortWeekdaysParse,a))?i:-1!==(i=Ye.call(this._minWeekdaysParse,a))?i:null:"ddd"===t?-1!==(i=Ye.call(this._shortWeekdaysParse,a))?i:-1!==(i=Ye.call(this._weekdaysParse,a))?i:-1!==(i=Ye.call(this._minWeekdaysParse,a))?i:null:-1!==(i=Ye.call(this._minWeekdaysParse,a))?i:-1!==(i=Ye.call(this._weekdaysParse,a))?i:-1!==(i=Ye.call(this._shortWeekdaysParse,a))?i:null}.call(this,e,t,n);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),s=0;s<7;s++){if(i=y([2e3,1]).day(s),n&&!this._fullWeekdaysParse[s]&&(this._fullWeekdaysParse[s]=new RegExp("^"+this.weekdays(i,"").replace(".","\\.?")+"$","i"),this._shortWeekdaysParse[s]=new RegExp("^"+this.weekdaysShort(i,"").replace(".","\\.?")+"$","i"),this._minWeekdaysParse[s]=new RegExp("^"+this.weekdaysMin(i,"").replace(".","\\.?")+"$","i")),this._weekdaysParse[s]||(r="^"+this.weekdays(i,"")+"|^"+this.weekdaysShort(i,"")+"|^"+this.weekdaysMin(i,""),this._weekdaysParse[s]=new RegExp(r.replace(".",""),"i")),n&&"dddd"===t&&this._fullWeekdaysParse[s].test(e))return s;if(n&&"ddd"===t&&this._shortWeekdaysParse[s].test(e))return s;if(n&&"dd"===t&&this._minWeekdaysParse[s].test(e))return s;if(!n&&this._weekdaysParse[s].test(e))return s}},yn.weekdaysRegex=function(e){return this._weekdaysParseExact?(m(this,"_weekdaysRegex")||Qe.call(this),e?this._weekdaysStrictRegex:this._weekdaysRegex):(m(this,"_weekdaysRegex")||(this._weekdaysRegex=qe),this._weekdaysStrictRegex&&e?this._weekdaysStrictRegex:this._weekdaysRegex)},yn.weekdaysShortRegex=function(e){return this._weekdaysParseExact?(m(this,"_weekdaysRegex")||Qe.call(this),e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(m(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=Je),this._weekdaysShortStrictRegex&&e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)},yn.weekdaysMinRegex=function(e){return this._weekdaysParseExact?(m(this,"_weekdaysRegex")||Qe.call(this),e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(m(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=Be),this._weekdaysMinStrictRegex&&e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)},yn.isPM=function(e){return"p"===(e+"").toLowerCase().charAt(0)},yn.meridiem=function(e,t,n){return 11<e?n?"pm":"PM":n?"am":"AM"},ut("en",{dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10;return e+(1===D(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")}}),c.lang=n("moment.lang is deprecated. Use moment.locale instead.",ut),c.langData=n("moment.langData is deprecated. Use moment.localeData instead.",ht);var wn=Math.abs;function Mn(e,t,n,s){var i=jt(t,n);return e._milliseconds+=s*i._milliseconds,e._days+=s*i._days,e._months+=s*i._months,e._bubble()}function kn(e){return e<0?Math.floor(e):Math.ceil(e)}function Sn(e){return 4800*e/146097}function Dn(e){return 146097*e/4800}function Yn(e){return function(){return this.as(e)}}var On=Yn("ms"),Tn=Yn("s"),bn=Yn("m"),xn=Yn("h"),Pn=Yn("d"),Wn=Yn("w"),Cn=Yn("M"),Hn=Yn("Q"),Rn=Yn("y");function Un(e){return function(){return this.isValid()?this._data[e]:NaN}}var Fn=Un("milliseconds"),Ln=Un("seconds"),Nn=Un("minutes"),Gn=Un("hours"),Vn=Un("days"),En=Un("months"),In=Un("years");var An=Math.round,jn={ss:44,s:45,m:45,h:22,d:26,M:11};var Zn=Math.abs;function zn(e){return(0<e)-(e<0)||+e}function $n(){if(!this.isValid())return this.localeData().invalidDate();var e,t,n=Zn(this._milliseconds)/1e3,s=Zn(this._days),i=Zn(this._months);t=S((e=S(n/60))/60),n%=60,e%=60;var r=S(i/12),a=i%=12,o=s,u=t,l=e,h=n?n.toFixed(3).replace(/\.?0+$/,""):"",d=this.asSeconds();if(!d)return"P0D";var c=d<0?"-":"",f=zn(this._months)!==zn(d)?"-":"",m=zn(this._days)!==zn(d)?"-":"",_=zn(this._milliseconds)!==zn(d)?"-":"";return c+"P"+(r?f+r+"Y":"")+(a?f+a+"M":"")+(o?m+o+"D":"")+(u||l||h?"T":"")+(u?_+u+"H":"")+(l?_+l+"M":"")+(h?_+h+"S":"")}var qn=Ht.prototype;return qn.isValid=function(){return this._isValid},qn.abs=function(){var e=this._data;return this._milliseconds=wn(this._milliseconds),this._days=wn(this._days),this._months=wn(this._months),e.milliseconds=wn(e.milliseconds),e.seconds=wn(e.seconds),e.minutes=wn(e.minutes),e.hours=wn(e.hours),e.months=wn(e.months),e.years=wn(e.years),this},qn.add=function(e,t){return Mn(this,e,t,1)},qn.subtract=function(e,t){return Mn(this,e,t,-1)},qn.as=function(e){if(!this.isValid())return NaN;var t,n,s=this._milliseconds;if("month"===(e=H(e))||"quarter"===e||"year"===e)switch(t=this._days+s/864e5,n=this._months+Sn(t),e){case"month":return n;case"quarter":return n/3;case"year":return n/12}else switch(t=this._days+Math.round(Dn(this._months)),e){case"week":return t/7+s/6048e5;case"day":return t+s/864e5;case"hour":return 24*t+s/36e5;case"minute":return 1440*t+s/6e4;case"second":return 86400*t+s/1e3;case"millisecond":return Math.floor(864e5*t)+s;default:throw new Error("Unknown unit "+e)}},qn.asMilliseconds=On,qn.asSeconds=Tn,qn.asMinutes=bn,qn.asHours=xn,qn.asDays=Pn,qn.asWeeks=Wn,qn.asMonths=Cn,qn.asQuarters=Hn,qn.asYears=Rn,qn.valueOf=function(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*D(this._months/12):NaN},qn._bubble=function(){var e,t,n,s,i,r=this._milliseconds,a=this._days,o=this._months,u=this._data;return 0<=r&&0<=a&&0<=o||r<=0&&a<=0&&o<=0||(r+=864e5*kn(Dn(o)+a),o=a=0),u.milliseconds=r%1e3,e=S(r/1e3),u.seconds=e%60,t=S(e/60),u.minutes=t%60,n=S(t/60),u.hours=n%24,o+=i=S(Sn(a+=S(n/24))),a-=kn(Dn(i)),s=S(o/12),o%=12,u.days=a,u.months=o,u.years=s,this},qn.clone=function(){return jt(this)},qn.get=function(e){return e=H(e),this.isValid()?this[e+"s"]():NaN},qn.milliseconds=Fn,qn.seconds=Ln,qn.minutes=Nn,qn.hours=Gn,qn.days=Vn,qn.weeks=function(){return S(this.days()/7)},qn.months=En,qn.years=In,qn.humanize=function(e){if(!this.isValid())return this.localeData().invalidDate();var t,n,s,i,r,a,o,u,l,h,d,c=this.localeData(),f=(n=!e,s=c,i=jt(t=this).abs(),r=An(i.as("s")),a=An(i.as("m")),o=An(i.as("h")),u=An(i.as("d")),l=An(i.as("M")),h=An(i.as("y")),(d=r<=jn.ss&&["s",r]||r<jn.s&&["ss",r]||a<=1&&["m"]||a<jn.m&&["mm",a]||o<=1&&["h"]||o<jn.h&&["hh",o]||u<=1&&["d"]||u<jn.d&&["dd",u]||l<=1&&["M"]||l<jn.M&&["MM",l]||h<=1&&["y"]||["yy",h])[2]=n,d[3]=0<+t,d[4]=s,function(e,t,n,s,i){return i.relativeTime(t||1,!!n,e,s)}.apply(null,d));return e&&(f=c.pastFuture(+this,f)),c.postformat(f)},qn.toISOString=$n,qn.toString=$n,qn.toJSON=$n,qn.locale=Xt,qn.localeData=en,qn.toIsoString=n("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",$n),qn.lang=Kt,I("X",0,0,"unix"),I("x",0,0,"valueOf"),ue("x",se),ue("X",/[+-]?\d+(\.\d{1,3})?/),ce("X",function(e,t,n){n._d=new Date(1e3*parseFloat(e,10))}),ce("x",function(e,t,n){n._d=new Date(D(e))}),c.version="2.24.0",e=bt,c.fn=mn,c.min=function(){return Wt("isBefore",[].slice.call(arguments,0))},c.max=function(){return Wt("isAfter",[].slice.call(arguments,0))},c.now=function(){return Date.now?Date.now():+new Date},c.utc=y,c.unix=function(e){return bt(1e3*e)},c.months=function(e,t){return vn(e,t,"months")},c.isDate=d,c.locale=ut,c.invalid=p,c.duration=jt,c.isMoment=k,c.weekdays=function(e,t,n){return pn(e,t,n,"weekdays")},c.parseZone=function(){return bt.apply(null,arguments).parseZone()},c.localeData=ht,c.isDuration=Rt,c.monthsShort=function(e,t){return vn(e,t,"monthsShort")},c.weekdaysMin=function(e,t,n){return pn(e,t,n,"weekdaysMin")},c.defineLocale=lt,c.updateLocale=function(e,t){if(null!=t){var n,s,i=st;null!=(s=ot(e))&&(i=s._config),(n=new P(t=x(i,t))).parentLocale=it[e],it[e]=n,ut(e)}else null!=it[e]&&(null!=it[e].parentLocale?it[e]=it[e].parentLocale:null!=it[e]&&delete it[e]);return it[e]},c.locales=function(){return s(it)},c.weekdaysShort=function(e,t,n){return pn(e,t,n,"weekdaysShort")},c.normalizeUnits=H,c.relativeTimeRounding=function(e){return void 0===e?An:"function"==typeof e&&(An=e,!0)},c.relativeTimeThreshold=function(e,t){return void 0!==jn[e]&&(void 0===t?jn[e]:(jn[e]=t,"s"===e&&(jn.ss=t-1),!0))},c.calendarFormat=function(e,t){var n=e.diff(t,"days",!0);return n<-6?"sameElse":n<-1?"lastWeek":n<0?"lastDay":n<1?"sameDay":n<2?"nextDay":n<7?"nextWeek":"sameElse"},c.prototype=mn,c.HTML5_FMT={DATETIME_LOCAL:"YYYY-MM-DDTHH:mm",DATETIME_LOCAL_SECONDS:"YYYY-MM-DDTHH:mm:ss",DATETIME_LOCAL_MS:"YYYY-MM-DDTHH:mm:ss.SSS",DATE:"YYYY-MM-DD",TIME:"HH:mm",TIME_SECONDS:"HH:mm:ss",TIME_MS:"HH:mm:ss.SSS",WEEK:"GGGG-[W]WW",MONTH:"YYYY-MM"},c});
composer.json CHANGED
@@ -5,8 +5,7 @@
5
  "s1lentium/iptools": "^1.1.0",
6
  "whichbrowser/parser": "2.0.37",
7
  "jaybizzle/crawler-detect": "1.2.78",
8
- "erusev/parsedown": "^1.6",
9
- "vectorface/whip": "0.3.2"
10
  },
11
  "config": {
12
  "vendor-dir": "includes/vendor"
5
  "s1lentium/iptools": "^1.1.0",
6
  "whichbrowser/parser": "2.0.37",
7
  "jaybizzle/crawler-detect": "1.2.78",
8
+ "erusev/parsedown": "^1.6"
 
9
  },
10
  "config": {
11
  "vendor-dir": "includes/vendor"
composer.lock ADDED
@@ -0,0 +1,481 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_readme": [
3
+ "This file locks the dependencies of your project to a known state",
4
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5
+ "This file is @generated automatically"
6
+ ],
7
+ "content-hash": "6497ec3331bd44e9dbbb7e43066a0708",
8
+ "packages": [
9
+ {
10
+ "name": "composer/ca-bundle",
11
+ "version": "1.1.4",
12
+ "source": {
13
+ "type": "git",
14
+ "url": "https://github.com/composer/ca-bundle.git",
15
+ "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d"
16
+ },
17
+ "dist": {
18
+ "type": "zip",
19
+ "url": "https://api.github.com/repos/composer/ca-bundle/zipball/558f321c52faeb4828c03e7dc0cfe39a09e09a2d",
20
+ "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d",
21
+ "shasum": ""
22
+ },
23
+ "require": {
24
+ "ext-openssl": "*",
25
+ "ext-pcre": "*",
26
+ "php": "^5.3.2 || ^7.0"
27
+ },
28
+ "require-dev": {
29
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5",
30
+ "psr/log": "^1.0",
31
+ "symfony/process": "^2.5 || ^3.0 || ^4.0"
32
+ },
33
+ "type": "library",
34
+ "extra": {
35
+ "branch-alias": {
36
+ "dev-master": "1.x-dev"
37
+ }
38
+ },
39
+ "autoload": {
40
+ "psr-4": {
41
+ "Composer\\CaBundle\\": "src"
42
+ }
43
+ },
44
+ "notification-url": "https://packagist.org/downloads/",
45
+ "license": [
46
+ "MIT"
47
+ ],
48
+ "authors": [
49
+ {
50
+ "name": "Jordi Boggiano",
51
+ "email": "j.boggiano@seld.be",
52
+ "homepage": "http://seld.be"
53
+ }
54
+ ],
55
+ "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
56
+ "keywords": [
57
+ "cabundle",
58
+ "cacert",
59
+ "certificate",
60
+ "ssl",
61
+ "tls"
62
+ ],
63
+ "time": "2019-01-28T09:30:10+00:00"
64
+ },
65
+ {
66
+ "name": "erusev/parsedown",
67
+ "version": "1.7.3",
68
+ "source": {
69
+ "type": "git",
70
+ "url": "https://github.com/erusev/parsedown.git",
71
+ "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7"
72
+ },
73
+ "dist": {
74
+ "type": "zip",
75
+ "url": "https://api.github.com/repos/erusev/parsedown/zipball/6d893938171a817f4e9bc9e86f2da1e370b7bcd7",
76
+ "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7",
77
+ "shasum": ""
78
+ },
79
+ "require": {
80
+ "ext-mbstring": "*",
81
+ "php": ">=5.3.0"
82
+ },
83
+ "require-dev": {
84
+ "phpunit/phpunit": "^4.8.35"
85
+ },
86
+ "type": "library",
87
+ "autoload": {
88
+ "psr-0": {
89
+ "Parsedown": ""
90
+ }
91
+ },
92
+ "notification-url": "https://packagist.org/downloads/",
93
+ "license": [
94
+ "MIT"
95
+ ],
96
+ "authors": [
97
+ {
98
+ "name": "Emanuil Rusev",
99
+ "email": "hello@erusev.com",
100
+ "homepage": "http://erusev.com"
101
+ }
102
+ ],
103
+ "description": "Parser for Markdown.",
104
+ "homepage": "http://parsedown.org",
105
+ "keywords": [
106
+ "markdown",
107
+ "parser"
108
+ ],
109
+ "time": "2019-03-17T18:48:37+00:00"
110
+ },
111
+ {
112
+ "name": "geoip2/geoip2",
113
+ "version": "v2.9.0",
114
+ "source": {
115
+ "type": "git",
116
+ "url": "https://github.com/maxmind/GeoIP2-php.git",
117
+ "reference": "a807fbf65212eef5d8d2db1a1b31082b53633d77"
118
+ },
119
+ "dist": {
120
+ "type": "zip",
121
+ "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/a807fbf65212eef5d8d2db1a1b31082b53633d77",
122
+ "reference": "a807fbf65212eef5d8d2db1a1b31082b53633d77",
123
+ "shasum": ""
124
+ },
125
+ "require": {
126
+ "maxmind-db/reader": "~1.0",
127
+ "maxmind/web-service-common": "~0.5",
128
+ "php": ">=5.4"
129
+ },
130
+ "require-dev": {
131
+ "friendsofphp/php-cs-fixer": "2.*",
132
+ "phpunit/phpunit": "4.*",
133
+ "squizlabs/php_codesniffer": "3.*"
134
+ },
135
+ "type": "library",
136
+ "autoload": {
137
+ "psr-4": {
138
+ "GeoIp2\\": "src"
139
+ }
140
+ },
141
+ "notification-url": "https://packagist.org/downloads/",
142
+ "license": [
143
+ "Apache-2.0"
144
+ ],
145
+ "authors": [
146
+ {
147
+ "name": "Gregory J. Oschwald",
148
+ "email": "goschwald@maxmind.com",
149
+ "homepage": "http://www.maxmind.com/"
150
+ }
151
+ ],
152
+ "description": "MaxMind GeoIP2 PHP API",
153
+ "homepage": "https://github.com/maxmind/GeoIP2-php",
154
+ "keywords": [
155
+ "IP",
156
+ "geoip",
157
+ "geoip2",
158
+ "geolocation",
159
+ "maxmind"
160
+ ],
161
+ "time": "2018-04-10T15:32:59+00:00"
162
+ },
163
+ {
164
+ "name": "jaybizzle/crawler-detect",
165
+ "version": "v1.2.78",
166
+ "source": {
167
+ "type": "git",
168
+ "url": "https://github.com/JayBizzle/Crawler-Detect.git",
169
+ "reference": "0f91f34d375ddd5d7034b9eaf6b74acd0b2a0ed6"
170
+ },
171
+ "dist": {
172
+ "type": "zip",
173
+ "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/0f91f34d375ddd5d7034b9eaf6b74acd0b2a0ed6",
174
+ "reference": "0f91f34d375ddd5d7034b9eaf6b74acd0b2a0ed6",
175
+ "shasum": ""
176
+ },
177
+ "require": {
178
+ "php": ">=5.3.0"
179
+ },
180
+ "require-dev": {
181
+ "phpunit/phpunit": "^4.8|^5.5|^6.5",
182
+ "satooshi/php-coveralls": "1.*"
183
+ },
184
+ "type": "library",
185
+ "autoload": {
186
+ "psr-4": {
187
+ "Jaybizzle\\CrawlerDetect\\": "src/"
188
+ }
189
+ },
190
+ "notification-url": "https://packagist.org/downloads/",
191
+ "license": [
192
+ "MIT"
193
+ ],
194
+ "authors": [
195
+ {
196
+ "name": "Mark Beech",
197
+ "email": "m@rkbee.ch",
198
+ "role": "Developer"
199
+ }
200
+ ],
201
+ "description": "CrawlerDetect is a PHP class for detecting bots/crawlers/spiders via the user agent",
202
+ "homepage": "https://github.com/JayBizzle/Crawler-Detect/",
203
+ "keywords": [
204
+ "crawler",
205
+ "crawler detect",
206
+ "crawler detector",
207
+ "crawlerdetect",
208
+ "php crawler detect"
209
+ ],
210
+ "time": "2019-01-15T21:21:27+00:00"
211
+ },
212
+ {
213
+ "name": "maxmind-db/reader",
214
+ "version": "v1.4.1",
215
+ "source": {
216
+ "type": "git",
217
+ "url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git",
218
+ "reference": "eb83d0ee1c1f9b8a340206302136bc81ee02ae74"
219
+ },
220
+ "dist": {
221
+ "type": "zip",
222
+ "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/eb83d0ee1c1f9b8a340206302136bc81ee02ae74",
223
+ "reference": "eb83d0ee1c1f9b8a340206302136bc81ee02ae74",
224
+ "shasum": ""
225
+ },
226
+ "require": {
227
+ "php": ">=5.4"
228
+ },
229
+ "require-dev": {
230
+ "friendsofphp/php-cs-fixer": "2.*",
231
+ "phpunit/phpunit": "4.* || 5.*",
232
+ "satooshi/php-coveralls": "1.0.*",
233
+ "squizlabs/php_codesniffer": "3.*"
234
+ },
235
+ "suggest": {
236
+ "ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder",
237
+ "ext-gmp": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder",
238
+ "ext-maxminddb": "A C-based database decoder that provides significantly faster lookups"
239
+ },
240
+ "type": "library",
241
+ "autoload": {
242
+ "psr-4": {
243
+ "MaxMind\\Db\\": "src/MaxMind/Db"
244
+ }
245
+ },
246
+ "notification-url": "https://packagist.org/downloads/",
247
+ "license": [
248
+ "Apache-2.0"
249
+ ],
250
+ "authors": [
251
+ {
252
+ "name": "Gregory J. Oschwald",
253
+ "email": "goschwald@maxmind.com",
254
+ "homepage": "http://www.maxmind.com/"
255
+ }
256
+ ],
257
+ "description": "MaxMind DB Reader API",
258
+ "homepage": "https://github.com/maxmind/MaxMind-DB-Reader-php",
259
+ "keywords": [
260
+ "database",
261
+ "geoip",
262
+ "geoip2",
263
+ "geolocation",
264
+ "maxmind"
265
+ ],
266
+ "time": "2019-01-04T19:55:56+00:00"
267
+ },
268
+ {
269
+ "name": "maxmind/web-service-common",
270
+ "version": "v0.5.0",
271
+ "source": {
272
+ "type": "git",
273
+ "url": "https://github.com/maxmind/web-service-common-php.git",
274
+ "reference": "61a9836fa3bb1743ab89752bae5005d71e78c73b"
275
+ },
276
+ "dist": {
277
+ "type": "zip",
278
+ "url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/61a9836fa3bb1743ab89752bae5005d71e78c73b",
279
+ "reference": "61a9836fa3bb1743ab89752bae5005d71e78c73b",
280
+ "shasum": ""
281
+ },
282
+ "require": {
283
+ "composer/ca-bundle": "^1.0.3",
284
+ "ext-curl": "*",
285
+ "ext-json": "*",
286
+ "php": ">=5.4"
287
+ },
288
+ "require-dev": {
289
+ "friendsofphp/php-cs-fixer": "2.*",
290
+ "phpunit/phpunit": "4.*",
291
+ "squizlabs/php_codesniffer": "3.*"
292
+ },
293
+ "type": "library",
294
+ "autoload": {
295
+ "psr-4": {
296
+ "MaxMind\\Exception\\": "src/Exception",
297
+ "MaxMind\\WebService\\": "src/WebService"
298
+ }
299
+ },
300
+ "notification-url": "https://packagist.org/downloads/",
301
+ "license": [
302
+ "Apache-2.0"
303
+ ],
304
+ "authors": [
305
+ {
306
+ "name": "Gregory Oschwald",
307
+ "email": "goschwald@maxmind.com"
308
+ }
309
+ ],
310
+ "description": "Internal MaxMind Web Service API",
311
+ "homepage": "https://github.com/maxmind/web-service-common-php",
312
+ "time": "2018-02-12T22:31:54+00:00"
313
+ },
314
+ {
315
+ "name": "psr/cache",
316
+ "version": "1.0.1",
317
+ "source": {
318
+ "type": "git",
319
+ "url": "https://github.com/php-fig/cache.git",
320
+ "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8"
321
+ },
322
+ "dist": {
323
+ "type": "zip",
324
+ "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8",
325
+ "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
326
+ "shasum": ""
327
+ },
328
+ "require": {
329
+ "php": ">=5.3.0"
330
+ },
331
+ "type": "library",
332
+ "extra": {
333
+ "branch-alias": {
334
+ "dev-master": "1.0.x-dev"
335
+ }
336
+ },
337
+ "autoload": {
338
+ "psr-4": {
339
+ "Psr\\Cache\\": "src/"
340
+ }
341
+ },
342
+ "notification-url": "https://packagist.org/downloads/",
343
+ "license": [
344
+ "MIT"
345
+ ],
346
+ "authors": [
347
+ {
348
+ "name": "PHP-FIG",
349
+ "homepage": "http://www.php-fig.org/"
350
+ }
351
+ ],
352
+ "description": "Common interface for caching libraries",
353
+ "keywords": [
354
+ "cache",
355
+ "psr",
356
+ "psr-6"
357
+ ],
358
+ "time": "2016-08-06T20:24:11+00:00"
359
+ },
360
+ {
361
+ "name": "s1lentium/iptools",
362
+ "version": "v1.1.1",
363
+ "source": {
364
+ "type": "git",
365
+ "url": "https://github.com/S1lentium/IPTools.git",
366
+ "reference": "f6f8ab6132ca7443bd7cced1681f5066d725fd5f"
367
+ },
368
+ "dist": {
369
+ "type": "zip",
370
+ "url": "https://api.github.com/repos/S1lentium/IPTools/zipball/f6f8ab6132ca7443bd7cced1681f5066d725fd5f",
371
+ "reference": "f6f8ab6132ca7443bd7cced1681f5066d725fd5f",
372
+ "shasum": ""
373
+ },
374
+ "require": {
375
+ "ext-bcmath": "*",
376
+ "php": ">=5.4.0"
377
+ },
378
+ "require-dev": {
379
+ "phpunit/phpunit": "~4.0",
380
+ "satooshi/php-coveralls": "~1.0"
381
+ },
382
+ "type": "library",
383
+ "autoload": {
384
+ "psr-4": {
385
+ "IPTools\\": "src/"
386
+ }
387
+ },
388
+ "notification-url": "https://packagist.org/downloads/",
389
+ "license": [
390
+ "MIT"
391
+ ],
392
+ "authors": [
393
+ {
394
+ "name": "Safarov Alisher",
395
+ "email": "alisher.safarov@outlook.com",
396
+ "homepage": "https://github.com/S1lentium"
397
+ }
398
+ ],
399
+ "description": "PHP Library for manipulating network addresses (IPv4 and IPv6)",
400
+ "keywords": [
401
+ "IP",
402
+ "IP-Tools",
403
+ "cidr",
404
+ "ipv4",
405
+ "ipv6",
406
+ "network",
407
+ "subnet"
408
+ ],
409
+ "time": "2018-09-19T06:15:53+00:00"
410
+ },
411
+ {
412
+ "name": "whichbrowser/parser",
413
+ "version": "v2.0.37",
414
+ "source": {
415
+ "type": "git",
416
+ "url": "https://github.com/WhichBrowser/Parser-PHP.git",
417
+ "reference": "9c6ad8eadc23294b1c66d92876c11f13c5d4cf48"
418
+ },
419
+ "dist": {
420
+ "type": "zip",
421
+ "url": "https://api.github.com/repos/WhichBrowser/Parser-PHP/zipball/9c6ad8eadc23294b1c66d92876c11f13c5d4cf48",
422
+ "reference": "9c6ad8eadc23294b1c66d92876c11f13c5d4cf48",
423
+ "shasum": ""
424
+ },
425
+ "require": {
426
+ "php": ">=5.4.0",
427
+ "psr/cache": "^1.0"
428
+ },
429
+ "require-dev": {
430
+ "icomefromthenet/reverse-regex": "0.0.6.3",
431
+ "phpunit/php-code-coverage": "^2.2|^3.0",
432
+ "phpunit/phpunit": "^4.0|^5.0",
433
+ "satooshi/php-coveralls": "^1.0",
434
+ "squizlabs/php_codesniffer": "2.5.*",
435
+ "symfony/yaml": ">=2.8"
436
+ },
437
+ "suggest": {
438
+ "cache/array-adapter": "Allows testing of the caching functionality"
439
+ },
440
+ "type": "library",
441
+ "autoload": {
442
+ "psr-4": {
443
+ "WhichBrowser\\": [
444
+ "src/",
445
+ "tests/src/"
446
+ ]
447
+ }
448
+ },
449
+ "notification-url": "https://packagist.org/downloads/",
450
+ "license": [
451
+ "MIT"
452
+ ],
453
+ "authors": [
454
+ {
455
+ "name": "Niels Leenheer",
456
+ "email": "niels@leenheer.nl",
457
+ "role": "Developer"
458
+ }
459
+ ],
460
+ "description": "Useragent sniffing library for PHP",
461
+ "homepage": "http://whichbrowser.net",
462
+ "keywords": [
463
+ "browser",
464
+ "sniffing",
465
+ "ua",
466
+ "useragent"
467
+ ],
468
+ "time": "2018-10-02T09:26:41+00:00"
469
+ }
470
+ ],
471
+ "packages-dev": [],
472
+ "aliases": [],
473
+ "minimum-stability": "stable",
474
+ "stability-flags": [],
475
+ "prefer-stable": false,
476
+ "prefer-lowest": false,
477
+ "platform": {
478
+ "php": ">=5.4"
479
+ },
480
+ "platform-dev": []
481
+ }
includes/classes/class-wp-statistics-admin.php CHANGED
@@ -658,4 +658,5 @@ class WP_Statistics_Admin {
658
  wp_mail( $WP_Statistics->get_option( 'email_list' ), sprintf( __( 'WP Statistics %s installed on', 'wp-statistics' ), WP_Statistics::$reg['version'] ) . ' ' . $blogname, __( 'Installation/upgrade complete!', 'wp-statistics' ), $headers );
659
  }
660
  }
 
661
  }
658
  wp_mail( $WP_Statistics->get_option( 'email_list' ), sprintf( __( 'WP Statistics %s installed on', 'wp-statistics' ), WP_Statistics::$reg['version'] ) . ' ' . $blogname, __( 'Installation/upgrade complete!', 'wp-statistics' ), $headers );
659
  }
660
  }
661
+
662
  }
includes/classes/class-wp-statistics-hits.php CHANGED
@@ -490,7 +490,7 @@ class WP_Statistics_Hits {
490
 
491
  // Now parse the referrer and store the results in the search table if the database has been converted.
492
  // Also make sure we actually inserted a row on the INSERT IGNORE above or we'll create duplicate entries.
493
- if ( $WP_Statistics->get_option( 'search_converted' ) && $wpdb->insert_id ) {
494
 
495
  $search_engines = wp_statistics_searchengine_list();
496
  if ( WP_Statistics_Rest::is_rest() ) {
490
 
491
  // Now parse the referrer and store the results in the search table if the database has been converted.
492
  // Also make sure we actually inserted a row on the INSERT IGNORE above or we'll create duplicate entries.
493
+ if ( $wpdb->insert_id ) {
494
 
495
  $search_engines = wp_statistics_searchengine_list();
496
  if ( WP_Statistics_Rest::is_rest() ) {
includes/classes/class-wp-statistics.php CHANGED
@@ -157,6 +157,32 @@ class WP_Statistics {
157
  }
158
  }
159
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  /**
161
  * Run when plugin loads
162
  */
@@ -704,6 +730,7 @@ class WP_Statistics {
704
  $options['disable_se_baidu'] = true;
705
  $options['disable_se_ask'] = true;
706
  $options['map_type'] = 'jqvmap';
 
707
 
708
  $options['force_robot_update'] = true;
709
 
@@ -781,18 +808,32 @@ class WP_Statistics {
781
  return $this->ip;
782
  }
783
 
 
 
 
784
  // Get User IP
785
- $whip = new \Vectorface\Whip\Whip( \Vectorface\Whip\Whip::PROXY_HEADERS | \Vectorface\Whip\Whip::REMOTE_ADDR );
786
- $whip->addCustomHeader( 'HTTP_CLIENT_IP' );
787
- $whip->addCustomHeader( 'HTTP_X_REAL_IP' );
788
- $user_ip = $whip->getValidIpAddress();
789
- if ( $user_ip != false ) {
790
- $this->ip = $user_ip;
 
 
 
 
 
 
 
 
 
791
  }
792
 
793
  // If no valid ip address has been found, use 127.0.0.1 (aka localhost).
794
- if ( false === $this->ip ) {
795
  $this->ip = '127.0.0.1';
 
 
796
  }
797
 
798
  return $this->ip;
@@ -819,6 +860,17 @@ class WP_Statistics {
819
  return $user_ip;
820
  }
821
 
 
 
 
 
 
 
 
 
 
 
 
822
  /**
823
  * Validate an IPv6 IP address
824
  *
157
  }
158
  }
159
 
160
+ /**
161
+ * List of $_SERVER
162
+ *
163
+ * @return array
164
+ */
165
+ public static function list_of_server_ip_variable() {
166
+ return array( 'REMOTE_ADDR', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'HTTP_X_REAL_IP', 'HTTP_X_CLUSTER_CLIENT_IP' );
167
+ }
168
+
169
+ /**
170
+ * Get Basis For Get User IP
171
+ */
172
+ public static function getIPMethod() {
173
+
174
+ // Set Default Method
175
+ $method = 'REMOTE_ADDR';
176
+
177
+ // Get Option
178
+ $wp_statistics = get_option( 'wp_statistics' );
179
+ if ( isset( $wp_statistics ) and is_array( $wp_statistics ) and isset( $wp_statistics['ip_method'] ) and trim( $wp_statistics['ip_method'] ) != "" ) {
180
+ $method = $wp_statistics['ip_method'];
181
+ }
182
+
183
+ return $method;
184
+ }
185
+
186
  /**
187
  * Run when plugin loads
188
  */
730
  $options['disable_se_baidu'] = true;
731
  $options['disable_se_ask'] = true;
732
  $options['map_type'] = 'jqvmap';
733
+ $options['ip_method'] = 'REMOTE_ADDR';
734
 
735
  $options['force_robot_update'] = true;
736
 
808
  return $this->ip;
809
  }
810
 
811
+ // Get User Set $_SERVER HEADER
812
+ $ip_method = self::getIPMethod();
813
+
814
  // Get User IP
815
+ if ( isset( $_SERVER[ $ip_method ] ) ) {
816
+ $this->ip = $_SERVER[ $ip_method ];
817
+ }
818
+
819
+ /**
820
+ * This Filter Used For Custom $_SERVER String
821
+ */
822
+ $user_ip = apply_filters( 'wp_statistics_sanitize_user_ip', $this->ip );
823
+
824
+ // Check If X_FORWARDED_FOR
825
+ foreach ( explode( ',', $user_ip ) as $ip ) {
826
+ $ip = trim( $ip );
827
+ if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false ) {
828
+ $user_ip = $ip;
829
+ }
830
  }
831
 
832
  // If no valid ip address has been found, use 127.0.0.1 (aka localhost).
833
+ if ( false === $user_ip ) {
834
  $this->ip = '127.0.0.1';
835
+ } else {
836
+ $this->ip = $user_ip;
837
  }
838
 
839
  return $this->ip;
860
  return $user_ip;
861
  }
862
 
863
+ /**
864
+ * Check IP contain Special Character
865
+ *
866
+ * @param $ip
867
+ * @return bool
868
+ */
869
+ public function check_sanitize_ip( $ip ) {
870
+ $preg = preg_replace( '/[^0-9- .:]/', '', $ip );
871
+ return $preg == $ip;
872
+ }
873
+
874
  /**
875
  * Validate an IPv6 IP address
876
  *
includes/functions/functions.php CHANGED
@@ -1329,6 +1329,64 @@ function wp_statistics_geoip_supported() {
1329
  return $enabled;
1330
  }
1331
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1332
  // This function creates the date range selector 'widget' used in the various statistics pages.
1333
  function wp_statistics_date_range_selector( $page, $current, $range = array(), $desc = array(), $extrafields = '', $pre_extra = '', $post_extra = '' ) {
1334
  GLOBAL $WP_Statistics;
@@ -1447,19 +1505,26 @@ function wp_statistics_date_range_selector( $page, $current, $range = array(), $
1447
  echo $post_extra;
1448
 
1449
  echo '</form>' . "\r\n";
 
1450
  echo '<script>
1451
- jQuery(function() {
1452
- //Get MYSQL Date
1453
- function wp_statistics_get_mysql_date(timestamp) {
1454
- var k = timestamp.valueOf() / 1000;
1455
- var t = new Date(k * 1000);
1456
- return t.getFullYear() + "-" + ("0" + (t.getMonth() + 1)).slice(-2) + "-" + ("0" + t.getDate()).slice(-2);
1457
- }
1458
  //From Date
1459
- jQuery( "#datestartpicker" ).datepicker({dateFormat: \'' . wp_statistics_dateformat_php_to_jqueryui( get_option( "date_format" ) ) . '\', onSelect: function(selectedDate) {var v = jQuery(this).val();var d = new Date(v);if (v.length > 0) {jQuery("#rangestart").val(wp_statistics_get_mysql_date(d));}}});
1460
- //To Date
1461
- jQuery( "#dateendpicker" ).datepicker({dateFormat: \'' . wp_statistics_dateformat_php_to_jqueryui( get_option( "date_format" ) ) . '\', onSelect: function(selectedDate) {var v = jQuery(this).val();var d = new Date(v);if (v.length > 0) {jQuery("#rangeend").val(wp_statistics_get_mysql_date(d));}}});
 
 
 
1462
  });
 
 
 
 
 
 
 
 
1463
  </script>' . "\r\n";
1464
  }
1465
 
1329
  return $enabled;
1330
  }
1331
 
1332
+ /**
1333
+ * Convert PHP date Format to Moment js
1334
+ *
1335
+ * @param $phpFormat
1336
+ * @return string
1337
+ * @see https://stackoverflow.com/questions/30186611/php-dateformat-to-moment-js-format
1338
+ */
1339
+ function wp_statistics_convert_php_to_moment_js( $phpFormat ) {
1340
+ $replacements = array(
1341
+ 'A' => 'A',
1342
+ 'a' => 'a',
1343
+ 'B' => '',
1344
+ 'c' => 'YYYY-MM-DD[T]HH:mm:ssZ',
1345
+ 'D' => 'ddd',
1346
+ 'd' => 'DD',
1347
+ 'e' => 'zz',
1348
+ 'F' => 'MMMM',
1349
+ 'G' => 'H',
1350
+ 'g' => 'h',
1351
+ 'H' => 'HH',
1352
+ 'h' => 'hh',
1353
+ 'I' => '',
1354
+ 'i' => 'mm',
1355
+ 'j' => 'D',
1356
+ 'L' => '',
1357
+ 'l' => 'dddd',
1358
+ 'M' => 'MMM',
1359
+ 'm' => 'MM',
1360
+ 'N' => 'E',
1361
+ 'n' => 'M',
1362
+ 'O' => 'ZZ',
1363
+ 'o' => 'YYYY',
1364
+ 'P' => 'Z',
1365
+ 'r' => 'ddd, DD MMM YYYY HH:mm:ss ZZ',
1366
+ 'S' => 'o',
1367
+ 's' => 'ss',
1368
+ 'T' => 'z',
1369
+ 't' => '',
1370
+ 'U' => 'X',
1371
+ 'u' => 'SSSSSS',
1372
+ 'v' => 'SSS',
1373
+ 'W' => 'W',
1374
+ 'w' => 'e',
1375
+ 'Y' => 'YYYY',
1376
+ 'y' => 'YY',
1377
+ 'Z' => '',
1378
+ 'z' => 'DDD'
1379
+ );
1380
+
1381
+ // Converts escaped characters.
1382
+ foreach ( $replacements as $from => $to ) {
1383
+ $replacements[ '\\' . $from ] = '[' . $from . ']';
1384
+ }
1385
+
1386
+ return strtr( $phpFormat, $replacements );
1387
+ }
1388
+
1389
+
1390
  // This function creates the date range selector 'widget' used in the various statistics pages.
1391
  function wp_statistics_date_range_selector( $page, $current, $range = array(), $desc = array(), $extrafields = '', $pre_extra = '', $post_extra = '' ) {
1392
  GLOBAL $WP_Statistics;
1505
  echo $post_extra;
1506
 
1507
  echo '</form>' . "\r\n";
1508
+ echo '<script src="' . WP_Statistics::$reg['plugin-url'] . 'assets/js/moment.min.js?ver=2.24.0"></script>';
1509
  echo '<script>
1510
+ jQuery(function() {
1511
+
 
 
 
 
 
1512
  //From Date
1513
+ jQuery( "#datestartpicker" ).datepicker({dateFormat: \'' . wp_statistics_dateformat_php_to_jqueryui( get_option( "date_format" ) ) . '\',
1514
+ onSelect: function(selectedDate) {
1515
+ if (selectedDate.length > 0) {
1516
+ jQuery("#rangestart").val(moment(selectedDate, \'' . wp_statistics_convert_php_to_moment_js( get_option( "date_format" ) ) . '\').format(\'YYYY-MM-DD\'));
1517
+ }
1518
+ }
1519
  });
1520
+ //To Date
1521
+ jQuery( "#dateendpicker" ).datepicker({
1522
+ dateFormat: \'' . wp_statistics_dateformat_php_to_jqueryui( get_option( "date_format" ) ) . '\',
1523
+ onSelect: function(selectedDate) {
1524
+ if (selectedDate.length > 0) {
1525
+ jQuery("#rangeend").val(moment(selectedDate, \'' . wp_statistics_convert_php_to_moment_js( get_option( "date_format" ) ) . '\').format(\'YYYY-MM-DD\'));
1526
+ }
1527
+ }});});
1528
  </script>' . "\r\n";
1529
  }
1530
 
includes/log/top-visitors.php CHANGED
@@ -30,16 +30,16 @@ include( WP_Statistics::$reg['plugin-dir'] . 'includes/log/widgets/top.visitors.
30
  echo '<input type="hidden" name="statsdate" id="stats-date" value="' . $rang_start . '">';
31
  echo '</form>' . "\r\n";
32
 
 
33
  echo '<script>
34
  jQuery(function() {
35
- //Get MYSQL Date
36
- function wp_statistics_get_mysql_date(timestamp) {
37
- var k = timestamp.valueOf() / 1000;
38
- var t = new Date(k * 1000);
39
- return t.getFullYear() + "-" + ("0" + (t.getMonth() + 1)).slice(-2) + "-" + ("0" + t.getDate()).slice(-2);
40
  }
41
- //From Date
42
- jQuery( "#statsdate" ).datepicker({dateFormat: \'' . wp_statistics_dateformat_php_to_jqueryui( get_option( "date_format" ) ) . '\', onSelect: function(selectedDate) {var v = jQuery(this).val();var d = new Date(v);if (v.length > 0) {jQuery("#stats-date").val(wp_statistics_get_mysql_date(d));}}});
43
  });
44
  </script>' . "\r\n";
45
 
30
  echo '<input type="hidden" name="statsdate" id="stats-date" value="' . $rang_start . '">';
31
  echo '</form>' . "\r\n";
32
 
33
+ echo '<script src="' . WP_Statistics::$reg['plugin-url'] . 'assets/js/moment.min.js?ver=2.24.0"></script>';
34
  echo '<script>
35
  jQuery(function() {
36
+ jQuery( "#statsdate" ).datepicker({dateFormat: \'' . wp_statistics_dateformat_php_to_jqueryui( get_option( "date_format" ) ) . '\',
37
+ onSelect: function(selectedDate) {
38
+ if (selectedDate.length > 0) {
39
+ jQuery("#stats-date").val(moment(selectedDate, \'' . wp_statistics_convert_php_to_moment_js( get_option( "date_format" ) ) . '\').format(\'YYYY-MM-DD\'));
40
+ }
41
  }
42
+ });
 
43
  });
44
  </script>' . "\r\n";
45
 
includes/settings/tabs/wps-visitor-ip.php ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // Save Option
4
+ if ( $wps_nonce_valid and $wps_admin ) {
5
+
6
+ $value = 'REMOTE_ADDR';
7
+ if ( isset( $_POST['ip_method'] ) and ! empty( $_POST['ip_method'] ) ) {
8
+
9
+ // Check Custom Header
10
+ if ( $_POST['ip_method'] == "CUSTOM_HEADER" ) {
11
+ if ( trim( $_POST['user_custom_header_ip_method'] ) != "" ) {
12
+ $value = $_POST['user_custom_header_ip_method'];
13
+ }
14
+ } else {
15
+ $value = $_POST['ip_method'];
16
+ }
17
+ }
18
+
19
+ $WP_Statistics->update_option( 'ip_method', $value );
20
+ }
21
+
22
+ // Get IP Method
23
+ $ip_method = WP_Statistics::getIPMethod();
24
+
25
+ // Add TickBox
26
+ add_thickbox();
27
+
28
+ ?>
29
+ <!-- Show Help $_SERVER -->
30
+ <div id="list-of-php-server" style="display:none;">
31
+ <table>
32
+ <tr>
33
+ <td width="330" style="color: #3238fb; border-bottom: 1px solid #bcbeff;padding-top:10px;padding-bottom:10px;">
34
+ <b><?php _e( '$_SERVER', 'wp-statistics' ); ?></b></td>
35
+ <td style="color: #3238fb; border-bottom: 1px solid #bcbeff;padding-top:10px;padding-bottom:10px;"> <?php _e( 'Value', 'wp-statistics' ); ?></td>
36
+ </tr>
37
+ <?php
38
+ foreach ( $_SERVER as $key => $value ) {
39
+ ?>
40
+ <tr>
41
+ <td width="330" style="padding-top:10px;padding-bottom:10px;">
42
+ <b><?php echo $key; ?></b></td>
43
+ <td style="padding-top:10px;padding-bottom:10px;"> <?php echo ( $value == "" ? "-" : substr( str_replace( array( "\n", "\r" ), '', trim( $value ) ), 0, 200 ) ) . ( strlen( $value ) > 200 ? '..' : '' ); ?></td>
44
+ </tr>
45
+ <?php
46
+ }
47
+ ?>
48
+ </table>
49
+ </div>
50
+ <table class="form-table">
51
+ <tbody>
52
+ <tr valign="top">
53
+ <th scope="row" colspan="2" style="padding-bottom: 10px; font-weight: normal;line-height: 25px;">
54
+ <?php _e( 'Your real IP detected with ipify.org service:', 'wp-statistics' ); ?>
55
+ </th>
56
+ </tr>
57
+
58
+ <tr valign="top">
59
+ <th scope="row" colspan="2">
60
+ <code id="user_real_ip" style="padding: 15px;font-size: 30px;font-weight: 200; letter-spacing: 2px;font-family: 'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',sans-serif;">
61
+ <script type="application/javascript">
62
+ jQuery(document).ready(function () {
63
+ jQuery.ajax({
64
+ url: "https://api.ipify.org?format=json",
65
+ dataType: 'json',
66
+ error: function (jqXHR) {
67
+ if (jqXHR.status == 0) {
68
+ jQuery("code#user_real_ip").html("<?php _e( 'Please check your internet connection and try again.', 'wp-statistics' ); ?>");
69
+ }
70
+ },
71
+ success: function (json) {
72
+ jQuery("code#user_real_ip").html(json['ip']);
73
+ }
74
+ });
75
+ });
76
+ </script>
77
+ </code></th>
78
+ </tr>
79
+
80
+ <tr>
81
+ <td colspan="3">
82
+ <p><?php _e( 'The items below return the IP address that is different on each server. Is the best way that you choose.', 'wp-statistics' ); ?></p>
83
+ </td>
84
+ </tr>
85
+
86
+ <?php
87
+ foreach ( WP_Statistics::list_of_server_ip_variable() as $method ) {
88
+ ?>
89
+ <tr valign="top">
90
+ <th scope="row" colspan="2" style="padding-top: 8px;padding-bottom: 8px;">
91
+ <table>
92
+ <tr>
93
+ <td style="width: 10px; padding: 0px;">
94
+ <input type="radio" name="ip_method" style="vertical-align: -3px;" value="<?php echo $method; ?>"<?php if ( $ip_method == $method ) {
95
+ echo " checked=\"checked\"";
96
+ } ?>>
97
+ </td>
98
+ <td style="width: 250px;"> <?php printf( __( 'Use %1$s', 'wp-statistics' ), $method ); ?></td>
99
+ <td><code><?php
100
+ if ( isset( $_SERVER[ $method ] ) and ! empty( $_SERVER[ $method ] ) ) {
101
+ echo $_SERVER[ $method ];
102
+ } else {
103
+ _e( 'No available data.', 'wp-statistics' );
104
+ } ?>
105
+ </code>
106
+ <?php
107
+ if ( isset( $_SERVER[ $method ] ) and ! empty( $_SERVER[ $method ] ) and $WP_Statistics->check_sanitize_ip( $_SERVER[ $method ] ) === false ) {
108
+ echo ' &nbsp;&nbsp;<a href="https://wp-statistics.com/sanitize-user-ip/" style="color: #d04f4f;" target="_blank" title="' . __( 'Your value required to sanitize user IP', 'wp-statistics' ) . '"><span class="dashicons dashicons-warning"></span></a>';
109
+ }
110
+ ?>
111
+ </td>
112
+ </tr>
113
+ </table>
114
+ </th>
115
+ </tr>
116
+ <?php
117
+ }
118
+ ?>
119
+
120
+ <!-- Custom Header -->
121
+ <tr valign="top">
122
+ <th scope="row" colspan="2" style="padding-top: 0px;padding-bottom: 0px;">
123
+ <table>
124
+ <tr>
125
+ <td style="width: 10px; padding: 0px;">
126
+ <input type="radio" name="ip_method" style="vertical-align: -3px;" value="CUSTOM_HEADER" <?php if ( ! in_array( $ip_method, WP_Statistics::list_of_server_ip_variable() ) ) {
127
+ echo " checked=\"checked\"";
128
+ } ?>>
129
+ </td>
130
+ <td style="width: 250px;"> <?php echo __( 'Use Custom Header', 'wp-statistics' ); ?></td>
131
+ <td style="padding-left: 0px;">
132
+ <input type="text" name="user_custom_header_ip_method" autocomplete="off" style="padding: 5px; width: 250px;height: 35px;" value="<?php if ( ! in_array( $ip_method, WP_Statistics::list_of_server_ip_variable() ) ) {
133
+ echo $ip_method;
134
+ } ?>">
135
+
136
+ <p class="description">
137
+ <?php if ( ! in_array( $ip_method, WP_Statistics::list_of_server_ip_variable() ) ) {
138
+ echo '<code>';
139
+ if ( isset( $_SERVER[ $ip_method ] ) and ! empty( $_SERVER[ $ip_method ] ) ) {
140
+ echo $_SERVER[ $ip_method ];
141
+ } else {
142
+ _e( 'No available data.', 'wp-statistics' );
143
+ }
144
+ }
145
+ echo '</code>';
146
+ if ( ! in_array( $ip_method, WP_Statistics::list_of_server_ip_variable() ) and isset( $_SERVER[ $ip_method ] ) and ! empty( $_SERVER[ $ip_method ] ) and $WP_Statistics->check_sanitize_ip( $_SERVER[ $ip_method ] ) === false ) {
147
+ echo ' &nbsp;&nbsp;<a href="https://wp-statistics.com/sanitize-user-ip/" style="color: #d04f4f;" target="_blank" title="' . __( 'Your value required to sanitize user IP', 'wp-statistics' ) . '"><span class="dashicons dashicons-warning"></span></a>';
148
+ }
149
+ ?></p>
150
+ <p class="description"><?php _e( 'If your server use the custom key in <code>$_SERVER</code> for getting IP. e.g. <code>HTTP_CF_CONNECTING_IP</code> in CloudFlare.', 'wp-statistics' ); ?></p>
151
+ <p class="description">
152
+ <a href="#TB_inline?&width=850&height=600&inlineId=list-of-php-server" class="thickbox"><?php _e( 'Show all <code>$_SERVER</code> in your server.', 'wp-statistics' ); ?></a>
153
+ </p>
154
+ </td>
155
+ </tr>
156
+ </table>
157
+ </th>
158
+ </tr>
159
+
160
+ </tbody>
161
+ </table>
162
+
163
+ <?php submit_button( __( 'Update', 'wp-statistics' ), 'primary', 'submit' );
includes/settings/wps-settings.php CHANGED
@@ -31,6 +31,7 @@ if ( $wps_admin === false ) {
31
  <ul class="tabs">
32
  <?php if ( $wps_admin ) { ?>
33
  <li class="tab-link current" data-tab="general-settings"><?php _e( 'General', 'wp-statistics' ); ?></li>
 
34
  <li class="tab-link" data-tab="privacy-settings"><?php _e( 'Privacy', 'wp-statistics' ); ?></li>
35
  <li class="tab-link" data-tab="notifications-settings"><?php _e( 'Notifications', 'wp-statistics' ); ?></li>
36
  <li class="tab-link" data-tab="overview-display-settings"><?php _e( 'Dashboard', 'wp-statistics' ); ?></li>
@@ -47,6 +48,9 @@ if ( $wps_admin === false ) {
47
  <div id="general-settings" class="tab-content current">
48
  <?php include( WP_Statistics::$reg['plugin-dir'] . 'includes/settings/tabs/wps-general.php' ); ?>
49
  </div>
 
 
 
50
  <div id="privacy-settings" class="tab-content">
51
  <?php include( WP_Statistics::$reg['plugin-dir'] . 'includes/settings/tabs/wps-privacy.php' ); ?>
52
  </div>
31
  <ul class="tabs">
32
  <?php if ( $wps_admin ) { ?>
33
  <li class="tab-link current" data-tab="general-settings"><?php _e( 'General', 'wp-statistics' ); ?></li>
34
+ <li class="tab-link" data-tab="visitor-ip-settings"><?php _e( 'Visitor IP', 'wp-statistics' ); ?></li>
35
  <li class="tab-link" data-tab="privacy-settings"><?php _e( 'Privacy', 'wp-statistics' ); ?></li>
36
  <li class="tab-link" data-tab="notifications-settings"><?php _e( 'Notifications', 'wp-statistics' ); ?></li>
37
  <li class="tab-link" data-tab="overview-display-settings"><?php _e( 'Dashboard', 'wp-statistics' ); ?></li>
48
  <div id="general-settings" class="tab-content current">
49
  <?php include( WP_Statistics::$reg['plugin-dir'] . 'includes/settings/tabs/wps-general.php' ); ?>
50
  </div>
51
+ <div id="visitor-ip-settings" class="tab-content">
52
+ <?php include( WP_Statistics::$reg['plugin-dir'] . 'includes/settings/tabs/wps-visitor-ip.php' ); ?>
53
+ </div>
54
  <div id="privacy-settings" class="tab-content">
55
  <?php include( WP_Statistics::$reg['plugin-dir'] . 'includes/settings/tabs/wps-privacy.php' ); ?>
56
  </div>
includes/templates/welcome.php CHANGED
@@ -30,54 +30,13 @@
30
  <section class="normal-section">
31
  <div class="left">
32
  <div class="content-padding">
33
- <h2><?php _e( 'Online Users Widget', 'wp-statistics' ); ?></h2>
34
- <p><?php _e( 'A cool widget to show current online users!', 'wp-statistics' ); ?></p>
35
  </div>
36
  </div>
37
 
38
  <div class="right text-center">
39
- <img src="<?php echo plugins_url( 'wp-statistics/assets/images/welcome/usersonline-widget.png' ); ?>"/>
40
- </div>
41
- </section>
42
-
43
- <section class="normal-section">
44
- <div class="right">
45
- <div class="content-padding">
46
- <h2><?php _e( 'Top Referring Sites', 'wp-statistics' ); ?></h2>
47
- <p><?php _e( 'Site icon, Server IP and minor improvements.', 'wp-statistics' ); ?></p>
48
- </div>
49
- </div>
50
-
51
- <div class="left text-center">
52
- <img src="<?php echo plugins_url( 'wp-statistics/assets/images/welcome/topreferring-widget.png' ); ?>"/>
53
- </div>
54
- </section>
55
-
56
- <section class="normal-section">
57
- <div class="left">
58
- <div class="content-padding">
59
- <h2><?php _e( 'Top 10 Counties', 'wp-statistics' ); ?></h2>
60
- <p><?php _e( 'You can see all visitor of a country!', 'wp-statistics' ); ?></p>
61
- </div>
62
- </div>
63
-
64
- <div class="right text-center">
65
- <img src="<?php echo plugins_url( 'wp-statistics/assets/images/welcome/topcountry-widget.png' ); ?>"/>
66
- </div>
67
- </section>
68
-
69
- <section class="normal-section" style="border-bottom: 0px none;">
70
- <div class="left">
71
- <div class="content-padding">
72
- <h2 style="margin-top: 0;"><?php _e( 'New changes', 'wp-statistics' ); ?></h2>
73
- </div>
74
- </div>
75
-
76
- <div class="right">
77
- <ul>
78
- <li>Fixed date picker issue in Top Visitors page.</li>
79
- <li>Improved: Minor issues.</li>
80
- </ul>
81
  </div>
82
  </section>
83
  </div>
30
  <section class="normal-section">
31
  <div class="left">
32
  <div class="content-padding">
33
+ <h2><?php _e( 'Visitor IP', 'wp-statistics' ); ?></h2>
34
+ <p><?php _e( 'Solve your IP problem with this option.', 'wp-statistics' ); ?></p>
35
  </div>
36
  </div>
37
 
38
  <div class="right text-center">
39
+ <img src="<?php echo plugins_url( 'wp-statistics/assets/images/welcome/visitor-ip.png' ); ?>"/>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  </div>
41
  </section>
42
  </div>
includes/vendor/composer/autoload_psr4.php CHANGED
@@ -7,9 +7,6 @@ $baseDir = dirname(dirname($vendorDir));
7
 
8
  return array(
9
  'WhichBrowser\\' => array($vendorDir . '/whichbrowser/parser/src', $vendorDir . '/whichbrowser/parser/tests/src'),
10
- 'Vectorface\\Whip\\' => array($vendorDir . '/vectorface/whip/src'),
11
- 'Vectorface\\WhipTests\\' => array($vendorDir . '/vectorface/whip/tests'),
12
- 'VectorFace\\Whip\\' => array($vendorDir . '/vectorface/whip/src'),
13
  'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
14
  'MaxMind\\WebService\\' => array($vendorDir . '/maxmind/web-service-common/src/WebService'),
15
  'MaxMind\\Exception\\' => array($vendorDir . '/maxmind/web-service-common/src/Exception'),
7
 
8
  return array(
9
  'WhichBrowser\\' => array($vendorDir . '/whichbrowser/parser/src', $vendorDir . '/whichbrowser/parser/tests/src'),
 
 
 
10
  'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
11
  'MaxMind\\WebService\\' => array($vendorDir . '/maxmind/web-service-common/src/WebService'),
12
  'MaxMind\\Exception\\' => array($vendorDir . '/maxmind/web-service-common/src/Exception'),
includes/vendor/composer/autoload_static.php CHANGED
@@ -11,12 +11,6 @@ class ComposerStaticInite38eff48d6e6d75cf438d3ef850d45a7
11
  array (
12
  'WhichBrowser\\' => 13,
13
  ),
14
- 'V' =>
15
- array (
16
- 'Vectorface\\Whip\\' => 16,
17
- 'Vectorface\\WhipTests\\' => 21,
18
- 'VectorFace\\Whip\\' => 16,
19
- ),
20
  'P' =>
21
  array (
22
  'Psr\\Cache\\' => 10,
@@ -51,18 +45,6 @@ class ComposerStaticInite38eff48d6e6d75cf438d3ef850d45a7
51
  0 => __DIR__ . '/..' . '/whichbrowser/parser/src',
52
  1 => __DIR__ . '/..' . '/whichbrowser/parser/tests/src',
53
  ),
54
- 'Vectorface\\Whip\\' =>
55
- array (
56
- 0 => __DIR__ . '/..' . '/vectorface/whip/src',
57
- ),
58
- 'Vectorface\\WhipTests\\' =>
59
- array (
60
- 0 => __DIR__ . '/..' . '/vectorface/whip/tests',
61
- ),
62
- 'VectorFace\\Whip\\' =>
63
- array (
64
- 0 => __DIR__ . '/..' . '/vectorface/whip/src',
65
- ),
66
  'Psr\\Cache\\' =>
67
  array (
68
  0 => __DIR__ . '/..' . '/psr/cache/src',
11
  array (
12
  'WhichBrowser\\' => 13,
13
  ),
 
 
 
 
 
 
14
  'P' =>
15
  array (
16
  'Psr\\Cache\\' => 10,
45
  0 => __DIR__ . '/..' . '/whichbrowser/parser/src',
46
  1 => __DIR__ . '/..' . '/whichbrowser/parser/tests/src',
47
  ),
 
 
 
 
 
 
 
 
 
 
 
 
48
  'Psr\\Cache\\' =>
49
  array (
50
  0 => __DIR__ . '/..' . '/psr/cache/src',
includes/vendor/composer/installed.json CHANGED
@@ -59,17 +59,17 @@
59
  },
60
  {
61
  "name": "erusev/parsedown",
62
- "version": "1.7.1",
63
- "version_normalized": "1.7.1.0",
64
  "source": {
65
  "type": "git",
66
  "url": "https://github.com/erusev/parsedown.git",
67
- "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1"
68
  },
69
  "dist": {
70
  "type": "zip",
71
- "url": "https://api.github.com/repos/erusev/parsedown/zipball/92e9c27ba0e74b8b028b111d1b6f956a15c01fc1",
72
- "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1",
73
  "shasum": ""
74
  },
75
  "require": {
@@ -79,7 +79,7 @@
79
  "require-dev": {
80
  "phpunit/phpunit": "^4.8.35"
81
  },
82
- "time": "2018-03-08T01:11:30+00:00",
83
  "type": "library",
84
  "installation-source": "dist",
85
  "autoload": {
@@ -417,64 +417,6 @@
417
  "subnet"
418
  ]
419
  },
420
- {
421
- "name": "vectorface/whip",
422
- "version": "v0.3.2",
423
- "version_normalized": "0.3.2.0",
424
- "source": {
425
- "type": "git",
426
- "url": "https://github.com/Vectorface/whip.git",
427
- "reference": "c3cdf71f532c83c3ab512cfa57130dad36fdbc83"
428
- },
429
- "dist": {
430
- "type": "zip",
431
- "url": "https://api.github.com/repos/Vectorface/whip/zipball/c3cdf71f532c83c3ab512cfa57130dad36fdbc83",
432
- "reference": "c3cdf71f532c83c3ab512cfa57130dad36fdbc83",
433
- "shasum": ""
434
- },
435
- "require": {
436
- "php": ">=5.3.0"
437
- },
438
- "require-dev": {
439
- "phpunit/phpunit": "~4.0",
440
- "psr/http-message": "~1.0",
441
- "squizlabs/php_codesniffer": "~2.0",
442
- "vectorface/dunit": "~2.0"
443
- },
444
- "time": "2017-10-30T14:05:31+00:00",
445
- "type": "library",
446
- "installation-source": "dist",
447
- "autoload": {
448
- "psr-4": {
449
- "Vectorface\\Whip\\": "./src",
450
- "VectorFace\\Whip\\": "./src",
451
- "Vectorface\\WhipTests\\": "./tests"
452
- }
453
- },
454
- "notification-url": "https://packagist.org/downloads/",
455
- "license": [
456
- "MIT"
457
- ],
458
- "authors": [
459
- {
460
- "name": "Daniel Bruce",
461
- "email": "dbruce@vectorface.com",
462
- "role": "Developer"
463
- },
464
- {
465
- "name": "Cory Darby",
466
- "email": "ckdarby@vectorface.com",
467
- "role": "Developer"
468
- }
469
- ],
470
- "description": "A PHP class for retrieving accurate IP address information for the client.",
471
- "homepage": "https://github.com/Vectorface/whip",
472
- "keywords": [
473
- "IP",
474
- "cdn",
475
- "cloudflare"
476
- ]
477
- },
478
  {
479
  "name": "whichbrowser/parser",
480
  "version": "v2.0.37",
59
  },
60
  {
61
  "name": "erusev/parsedown",
62
+ "version": "1.7.3",
63
+ "version_normalized": "1.7.3.0",
64
  "source": {
65
  "type": "git",
66
  "url": "https://github.com/erusev/parsedown.git",
67
+ "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7"
68
  },
69
  "dist": {
70
  "type": "zip",
71
+ "url": "https://api.github.com/repos/erusev/parsedown/zipball/6d893938171a817f4e9bc9e86f2da1e370b7bcd7",
72
+ "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7",
73
  "shasum": ""
74
  },
75
  "require": {
79
  "require-dev": {
80
  "phpunit/phpunit": "^4.8.35"
81
  },
82
+ "time": "2019-03-17T18:48:37+00:00",
83
  "type": "library",
84
  "installation-source": "dist",
85
  "autoload": {
417
  "subnet"
418
  ]
419
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
  {
421
  "name": "whichbrowser/parser",
422
  "version": "v2.0.37",
includes/vendor/erusev/parsedown/Parsedown.php CHANGED
@@ -17,7 +17,7 @@ class Parsedown
17
  {
18
  # ~
19
 
20
- const version = '1.7.1';
21
 
22
  # ~
23
 
@@ -429,7 +429,21 @@ class Parsedown
429
 
430
  if (isset($matches[1]))
431
  {
432
- $class = 'language-'.$matches[1];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
433
 
434
  $Element['attributes'] = array(
435
  'class' => $class,
17
  {
18
  # ~
19
 
20
+ const version = '1.7.3';
21
 
22
  # ~
23
 
429
 
430
  if (isset($matches[1]))
431
  {
432
+ /**
433
+ * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
434
+ * Every HTML element may have a class attribute specified.
435
+ * The attribute, if specified, must have a value that is a set
436
+ * of space-separated tokens representing the various classes
437
+ * that the element belongs to.
438
+ * [...]
439
+ * The space characters, for the purposes of this specification,
440
+ * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
441
+ * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
442
+ * U+000D CARRIAGE RETURN (CR).
443
+ */
444
+ $language = substr($matches[1], 0, strcspn($matches[1], " \t\n\f\r"));
445
+
446
+ $class = 'language-'.$language;
447
 
448
  $Element['attributes'] = array(
449
  'class' => $class,
includes/vendor/vectorface/whip/.dunitconfig DELETED
@@ -1,19 +0,0 @@
1
- ; the list of docker images to run against
2
- images="
3
- vectorface/php5.3
4
- vectorface/php5.4
5
- vectorface/php5.5
6
- vectorface/php5.6
7
- vectorface/php7.0
8
- vectorface/hhvm";
9
-
10
-
11
- ; a flag indicating whether to run the syntax check
12
- checkSyntax="true";
13
- ; the syntax command to execute
14
- syntaxCommand="find /opt/source -type f -name \"*.php\" ! -path \"*/vendor/*\" -print0 | xargs -0 -n 1 -P 8 php -l | grep -v 'No syntax errors'"
15
-
16
- ; a flag indicating whether to run the phpunit test suite
17
- runTests="true";
18
- ; the phpunit command to execute
19
- unitTestsCommand="/opt/source/vendor/bin/phpunit"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/vendor/vectorface/whip/.gitignore DELETED
@@ -1,4 +0,0 @@
1
- composer.lock
2
- composer.phar
3
- coverage/
4
- vendor/
 
 
 
 
includes/vendor/vectorface/whip/.scrutinizer.yml DELETED
@@ -1,6 +0,0 @@
1
- tools:
2
- external_code_coverage: true
3
-
4
- checks:
5
- php:
6
- code_rating: true
 
 
 
 
 
 
includes/vendor/vectorface/whip/.travis.yml DELETED
@@ -1,29 +0,0 @@
1
- language: php
2
-
3
- php:
4
- - 7.1
5
- - 7.0
6
- - 5.6
7
- - 5.5
8
- - 5.4
9
- - hhvm
10
-
11
- matrix:
12
- allow_failures:
13
- - php: hhvm
14
- fast_finish: true
15
- include:
16
- - php: 5.3
17
- dist: precise
18
-
19
- before_script:
20
- - composer self-update
21
- - composer install
22
-
23
- script:
24
- - ./vendor/bin/phpunit --coverage-clover=coverage.clover
25
- - ./vendor/bin/phpcs --standard=PSR2 src/ tests/
26
-
27
- after_script:
28
- - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then wget https://scrutinizer-ci.com/ocular.phar; fi;'
29
- - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/vendor/vectorface/whip/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2015 Vectorface, Inc.
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/vendor/vectorface/whip/README.md DELETED
@@ -1,237 +0,0 @@
1
- # Whip
2
-
3
- [![Build Status](https://travis-ci.org/Vectorface/whip.svg?branch=master)](https://travis-ci.org/Vectorface/whip)
4
- [![Code Coverage](https://scrutinizer-ci.com/g/Vectorface/whip/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/Vectorface/whip/?branch=master)
5
- [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Vectorface/whip/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Vectorface/whip/?branch=master)
6
- [![Latest Stable Version](https://poser.pugx.org/vectorface/whip/v/stable.svg)](https://packagist.org/packages/vectorface/whip)
7
- [![License](https://poser.pugx.org/vectorface/whip/license.svg)](https://packagist.org/packages/vectorface/whip)
8
-
9
- Whip (stands for Which Ip) is a lightweight class for returning a client's IP address in PHP.
10
-
11
- ## The Problem
12
-
13
- It may seem trivial to simply pull the client's IP address from
14
- `$_SERVER['REMOTE_ADDR']` but this address is not always accurate. For example,
15
- if your web servers are behind a reverse proxy like Varnish, the IP address
16
- listed will be that of your proxy and not the client.
17
-
18
- Many solutions propose checking multiple headers but those headers can be
19
- spoofed as well and we want to present a final solution anyone can deploy.
20
-
21
- ## Installing Whip.
22
-
23
- Simply run the following [composer](https://getcomposer.org/) command:
24
-
25
- ```shell
26
- $ composer require vectorface/whip
27
- ```
28
-
29
- ## Using Whip
30
-
31
- Add the required `use` statement to your class
32
-
33
- ```php
34
- use Vectorface\Whip\Whip;
35
- ```
36
-
37
- To fetch an IP address using every implemented method, you can simply do
38
-
39
- ```php
40
- $whip = new Whip();
41
- $clientAddress = $whip->getValidIpAddress();
42
- ```
43
-
44
- The class will attempt every method to retrieve the client's IP address
45
- starting with very specific use cases and falling back to more general use
46
- cases.
47
-
48
- Note, that the method `Whip::getValidIpAddress` will return `false` if no
49
- valid IP address could be determined, so it is important to check for errors.
50
-
51
- ```php
52
- $whip = new Whip();
53
- if (false === ($clientAddress = $whip->getValidIpAddress())) {
54
- // handle the error
55
- }
56
- ```
57
-
58
- To fetch an IP address using a specific method, you can pass a bitmask of
59
- enabled methods to the constructor. Here is an example of looking up the IP
60
- address using CloudFlare's custom HTTP header, and falling back to
61
- `$_SERVER['REMOTE_ADDR']` otherwise.
62
-
63
- ```php
64
- $whip = new Whip(Whip::CLOUDFLARE_HEADERS | Whip::REMOTE_ADDR);
65
- $clientAddress = $whip->getValidIpAddress();
66
- ```
67
-
68
- This method works, but there is the problem that the custom HTTP header can
69
- easily be spoofed if your sites accept traffic not from CloudFlare. To prevent
70
- this, Whip allows you to specify a whitelist of IP addresses (or address ranges)
71
- that you accept per method.
72
-
73
- ## Using Whip Behind a Trusted Proxy
74
-
75
- A common use case is to deploy a trusted proxy (nginx, varnish, and many others)
76
- in front of an application server. To forward the correct client IP, the trusted
77
- proxy should be configured to inject a header for Whip to read with the custom
78
- headers method.
79
-
80
- If the trusted proxy is configured to send a X-My-Client-IP header, Whip
81
- could be used as follows:
82
-
83
- ```php
84
- $whip = new Whip(
85
- Whip::CUSTOM_HEADERS,
86
- [Whip::CUSTOM_HEADERS => [ // Whitelist your proxies.
87
- Whip::IPV4 => ['10.0.0.2', '10.0.0.3']
88
- ]]
89
- );
90
- $whip->addCustomHeader('HTTP_X_MY_CLIENT_IP');
91
- $ip = $whip->getValidIpAddress();
92
- ```
93
-
94
- ## Using the CloudFlare IP Range Whitelist
95
-
96
- As a common example, Whip can accept a whitelist of IP ranges for CloudFlare
97
- when using their custom header and fall back to `$_SERVER['REMOTE_ADDR']` if the
98
- custom header was not found or if the source IP address does match any in the
99
- whitelist.
100
-
101
- ```php
102
- $whip = new Whip(
103
- Whip::CLOUDFLARE_HEADERS | Whip::REMOTE_ADDR,
104
- [
105
- Whip::CLOUDFLARE_HEADERS => [
106
- Whip::IPV4 => [
107
- '199.27.128.0/21',
108
- '173.245.48.0/20',
109
- '103.21.244.0/22',
110
- '103.22.200.0/22',
111
- '103.31.4.0/22',
112
- '141.101.64.0/18',
113
- '108.162.192.0/18',
114
- '190.93.240.0/20',
115
- '188.114.96.0/20',
116
- '197.234.240.0/22',
117
- '198.41.128.0/17',
118
- '162.158.0.0/15',
119
- '104.16.0.0/12'
120
- ],
121
- Whip::IPV6 => [
122
- '2400:cb00::/32',
123
- '2606:4700::/32',
124
- '2803:f800::/32',
125
- '2405:b500::/32',
126
- '2405:8100::/32'
127
- ]
128
- ]
129
- ]
130
- );
131
- $clientAddress = $whip->getValidIpAddress();
132
- ```
133
-
134
- Please be sure to use the actual list of IP ranges from CloudFlare for
135
- [IPv4](https://www.cloudflare.com/ips-v4) and
136
- [IPv6](https://www.cloudflare.com/ips-v6).
137
-
138
- ## List of Methods
139
-
140
- The individual methods are stored as integer constants on the `Whip` class.
141
- To combine methods, use the bitwise OR operator `|`. The current methods are:
142
-
143
- - `Whip::REMOTE_ADDR` - Uses the standard `$_SERVER['REMOTE_ADDR']`.
144
- - `Whip::PROXY_HEADERS` - Uses any of the following values:
145
- - `$_SERVER['HTTP_CLIENT_IP']`
146
- - `$_SERVER['HTTP_X_FORWARDED_FOR']`
147
- - `$_SERVER['HTTP_X_FORWARDED']`
148
- - `$_SERVER['HTTP_X_CLUSTER_CLIENT_IP']`
149
- - `$_SERVER['HTTP_FORWARDED_FOR']`
150
- - `$_SERVER['HTTP_FORWARDED']`
151
- - `$_SERVER['HTTP_X_REAL_IP']`
152
- - `Whip::CLOUDFLARE_HEADERS` - Uses the CloudFlare provided HTTP header
153
- "CF-Connecting-IP".
154
- - `Whip::INCAPSULA_HEADERS` - Use the Incapsula provided HTTP header
155
- "Incap-Client-IP".
156
- - `Whip::CUSTOM_HEADERS` - Uses a custom list of HTTP headers passed into
157
- `Whip::addCustomHeader`.
158
-
159
- Please note that the proxy headers method can be susceptible to client spoofing
160
- because it extracts addresses from several possible HTTP headers. This means
161
- that using the proxy headers method is not appropriate where trust is required,
162
- like in the context of authentication.
163
-
164
- ## Using a Custom Header
165
-
166
- Whip can also allow you to specify a custom header to use. For example, you may
167
- configure your own proxy to send a unique obfuscated header internally that
168
- would be hard to spoof. In this example, we assume Varnish is run locally and
169
- we use a custom HTTP header "X-SECRET-REAL-IP" (and fall back to
170
- `$_SERVER['REMOTE_ADDR']` if the custom header doesn't work).
171
-
172
- ```php
173
- $whip = new Whip(
174
- Whip::CUSTOM_HEADERS | Whip::REMOTE_ADDR,
175
- [
176
- Whip::CUSTOM_HEADERS => [
177
- Whip::IPV4 => [
178
- '127.0.0.1'
179
- ],
180
- Whip::IPV6 => [
181
- '::1'
182
- ]
183
- ]
184
- ]
185
- );
186
- $whip->addCustomHeader('X-SECRET-REAL-IP');
187
- $clientAddress = $whip->getValidIpAddress();
188
- ```
189
-
190
- ## Valid IP Ranges
191
-
192
- For IPv4, Whip accepts three types of IP ranges:
193
-
194
- - Asterisk wildcard (192.168.\*)
195
- - Dashed range (192.168.0.0-192.168.255.255)
196
- - CIDR bitmask notation (192.168.0.0/16)
197
-
198
- For IPv6, Whip only accepts the CIDR bitmask notation (fc00::/7).
199
-
200
- Furthermore, you can specify a list of exact IP addresses instead of a list of
201
- ranges.
202
-
203
- ## IP Range Filtering
204
-
205
- Whip can also be used to provide simple IP range matching. For example,
206
-
207
- ```php
208
- $range = new Vectorface\Whip\IpRange\Ipv4Range('10.0.*');
209
- if ($range->containsIp($ipv4Address)) {
210
- // handle the IP address being within the range
211
- }
212
-
213
- $range = new Vectorface\Whip\IpRange\Ipv6Range('::1/32');
214
- if ($range->containsIp($ipv6Address)) {
215
- // handle the IP address being within the range
216
- }
217
- ```
218
-
219
- ## PSR-7 Requests, and Others
220
-
221
- Whip supports using [PSR-7 (http-message)](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md) request instances in place of the `$_SERVER` superglobal. For example,
222
-
223
- ```php
224
- // Get a Psr\Http\Message\ServerRequestInterface implementation from somewhere.
225
- $request = ServerRequestFactory::fromGlobals();
226
-
227
- // You can pass the request in the constructor.
228
- $whip = new Whip(Whip::REMOTE_ADDR, [], $request);
229
-
230
- // ... or set the request as the source of data.
231
- $whip->setSource($request);
232
-
233
- // ... or pass it to any function accepting a source argument.
234
- $ip = $whip->getValidIpAddress($request);
235
- ```
236
-
237
- Other request formats can be supported via a RequestAdapter (src/Request/RequestAdapter) implementation.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/vendor/vectorface/whip/composer.json DELETED
@@ -1,42 +0,0 @@
1
- {
2
- "name": "vectorface/whip",
3
- "description": "A PHP class for retrieving accurate IP address information for the client.",
4
- "keywords": [
5
- "IP", "Cloudflare", "CDN"
6
- ],
7
- "type": "library",
8
- "license": "MIT",
9
- "authors": [
10
- {
11
- "name": "Daniel Bruce",
12
- "email": "dbruce@vectorface.com",
13
- "role": "Developer"
14
- },
15
- {
16
- "name": "Cory Darby",
17
- "email": "ckdarby@vectorface.com",
18
- "role": "Developer"
19
- }
20
- ],
21
- "autoload": {
22
- "psr-4": {
23
- "Vectorface\\Whip\\": "./src",
24
- "VectorFace\\Whip\\": "./src",
25
- "Vectorface\\WhipTests\\": "./tests"
26
- }
27
- },
28
- "homepage": "https://github.com/Vectorface/whip",
29
- "support": {
30
- "issues": "https://github.com/Vectorface/whip/issues",
31
- "source": "https://github.com/Vectorface/whip"
32
- },
33
- "require": {
34
- "php": ">=5.3.0"
35
- },
36
- "require-dev": {
37
- "phpunit/phpunit": "~4.0",
38
- "squizlabs/php_codesniffer": "~2.0",
39
- "vectorface/dunit": "~2.0",
40
- "psr/http-message": "~1.0"
41
- }
42
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/vendor/vectorface/whip/phpunit.xml DELETED
@@ -1,21 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
- xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
4
- backupGlobals="false"
5
- colors="true"
6
- bootstrap="tests/bootstrap.php">
7
- <testsuites>
8
- <testsuite name="Main suite">
9
- <directory>tests/</directory>
10
- </testsuite>
11
- </testsuites>
12
-
13
- <filter>
14
- <whitelist processUncoveredFilesFromWhitelist="true">
15
- <directory suffix=".php">src/</directory>
16
- <exclude>
17
- <directory suffix=".php">vendor/</directory>
18
- </exclude>
19
- </whitelist>
20
- </filter>
21
- </phpunit>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/vendor/vectorface/whip/src/IpRange/IpRange.php DELETED
@@ -1,43 +0,0 @@
1
- <?php
2
-
3
- /*
4
- The MIT License (MIT)
5
-
6
- Copyright (c) 2015 Vectorface, Inc.
7
-
8
- Permission is hereby granted, free of charge, to any person obtaining a copy
9
- of this software and associated documentation files (the "Software"), to deal
10
- in the Software without restriction, including without limitation the rights
11
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
- copies of the Software, and to permit persons to whom the Software is
13
- furnished to do so, subject to the following conditions:
14
-
15
- The above copyright notice and this permission notice shall be included in
16
- all copies or substantial portions of the Software.
17
-
18
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
- THE SOFTWARE.
25
- */
26
-
27
- namespace Vectorface\Whip\IpRange;
28
-
29
- /**
30
- * An interface for IP ranges.
31
- * @copyright Vectorface, Inc 2015
32
- * @author Daniel Bruce <dbruce1126@gmail.com>
33
- */
34
- interface IpRange
35
- {
36
- /**
37
- * Returns whether or not a given IP address falls within this range.
38
- * @param string $ipAddress The given IP address.
39
- * @return boolean Returns true if the IP address falls within the range
40
- * and false otherwise.
41
- */
42
- public function containsIp($ipAddress);
43
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/vendor/vectorface/whip/src/IpRange/IpWhitelist.php DELETED
@@ -1,121 +0,0 @@
1
- <?php
2
-
3
- /*
4
- The MIT License (MIT)
5
-
6
- Copyright (c) 2015 Vectorface, Inc.
7
-
8
- Permission is hereby granted, free of charge, to any person obtaining a copy
9
- of this software and associated documentation files (the "Software"), to deal
10
- in the Software without restriction, including without limitation the rights
11
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
- copies of the Software, and to permit persons to whom the Software is
13
- furnished to do so, subject to the following conditions:
14
-
15
- The above copyright notice and this permission notice shall be included in
16
- all copies or substantial portions of the Software.
17
-
18
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
- THE SOFTWARE.
25
- */
26
-
27
- namespace Vectorface\Whip\IpRange;
28
-
29
- /**
30
- * A class representing the list of whitelisted IP addresses.
31
- * @copyright Vectorface, Inc 2015
32
- * @author Daniel Bruce <dbruce1126@gmail.com>
33
- */
34
- class IpWhitelist
35
- {
36
- /** The whitelist key for IPv4 addresses */
37
- const IPV4 = 'ipv4';
38
-
39
- /** The whitelist key for IPv6 addresses */
40
- const IPV6 = 'ipv6';
41
-
42
- /** an array of Ipv4Range items */
43
- private $ipv4Whitelist;
44
-
45
- /** an array of Ipv6Range items */
46
- private $ipv6Whitelist;
47
-
48
- /**
49
- * Constructor for the class.
50
- * @param array $whitelists An array with two keys ('ipv4' and 'ipv6') with
51
- * each key mapping to an array of valid IP ranges.
52
- */
53
- public function __construct(array $whitelists)
54
- {
55
- $this->ipv4Whitelist = $this->constructWhiteListForKey(
56
- $whitelists,
57
- self::IPV4,
58
- 'Vectorface\\Whip\\IpRange\\Ipv4Range'
59
- );
60
- $this->ipv6Whitelist = $this->constructWhiteListForKey(
61
- $whitelists,
62
- self::IPV6,
63
- 'Vectorface\\Whip\\IpRange\\Ipv6Range'
64
- );
65
- }
66
-
67
- /**
68
- * Returns whether or not the given IP address is within the whitelist.
69
- * @param string $ipAddress A valid IPv4 or IPv6 address.
70
- * @return boolean Returns true if the IP address matches one of the
71
- * whitelisted IP ranges and false otherwise.
72
- */
73
- public function isIpWhitelisted($ipAddress)
74
- {
75
- // determine whether this IP is IPv4 or IPv6
76
- $isIpv4Address = filter_var($ipAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
77
- return $this->isIpInWhitelist(
78
- ($isIpv4Address) ? $this->ipv4Whitelist : $this->ipv6Whitelist,
79
- $ipAddress
80
- );
81
- }
82
-
83
- /**
84
- * Constructs the whitelist for the given key. Each element in the
85
- * whitelist gets mapped from a string to an instance of an Ipv4Range or
86
- * Ipv6Range.
87
- * @param array $whitelist The input whitelist of ranges.
88
- * @param string $key The key to use from the input whitelist ('ipv4' or
89
- * 'ipv6').
90
- * @param string $class Each range string gets mapped to an instance of the
91
- * specified $class.
92
- * @return array Returns an array of Ipv4Range or Ipv6Range elements.
93
- */
94
- private function constructWhiteListForKey(array $whitelist, $key, $class)
95
- {
96
- if (isset($whitelist[$key]) && is_array($whitelist[$key])) {
97
- return array_map(function ($range) use ($class) {
98
- return new $class($range);
99
- }, array_values($whitelist[$key]));
100
- } else {
101
- return array();
102
- }
103
- }
104
-
105
- /**
106
- * Returns whether or not the given IP address is in the given whitelist.
107
- * @param array $whitelist The given whitelist.
108
- * @param string $ipAddress The given IP address.
109
- * @return boolean Returns true if the IP address is in the whitelist and
110
- * false otherwise.
111
- */
112
- private function isIpInWhitelist(array $whitelist, $ipAddress)
113
- {
114
- foreach ($whitelist as $ipRange) {
115
- if ($ipRange->containsIp($ipAddress)) {
116
- return true;
117
- }
118
- }
119
- return false;
120
- }
121
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/vendor/vectorface/whip/src/IpRange/Ipv4Range.php DELETED
@@ -1,152 +0,0 @@
1
- <?php
2
-
3
- /*
4
- The MIT License (MIT)
5
-
6
- Copyright (c) 2015 Vectorface, Inc.
7
-
8
- Permission is hereby granted, free of charge, to any person obtaining a copy
9
- of this software and associated documentation files (the "Software"), to deal
10
- in the Software without restriction, including without limitation the rights
11
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
- copies of the Software, and to permit persons to whom the Software is
13
- furnished to do so, subject to the following conditions:
14
-
15
- The above copyright notice and this permission notice shall be included in
16
- all copies or substantial portions of the Software.
17
-
18
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
- THE SOFTWARE.
25
- */
26
-
27
- namespace Vectorface\Whip\IpRange;
28
-
29
- /**
30
- * A class representing an IPv4 address range.
31
- * @copyright Vectorface, Inc 2015
32
- * @author Daniel Bruce <dbruce1126@gmail.com>
33
- */
34
- class Ipv4Range implements IpRange
35
- {
36
-
37
- /** the lower value of the range (as a long integer) */
38
- private $lowerInt;
39
- /** the upper value of the range (as a long integer) */
40
- private $upperInt;
41
-
42
- /**
43
- * Constructor for the class.
44
- * @param string $range A valid IPv4 range as a string. Supported range styles:
45
- * - CIDR notation (127.0.0.1/24)
46
- * - hyphen notation (127.0.0.1-127.0.0.255)
47
- * - wildcard notation (127.0.0.*)
48
- * - a single specific IP address (127.0.0.1)
49
- */
50
- public function __construct($range)
51
- {
52
- $this->computeLowerAndUpperBounds($range);
53
- }
54
-
55
- /**
56
- * Returns the lower value of the IPv4 range as a long integer.
57
- * @return int The lower value of the IPv4 range.
58
- */
59
- public function getLowerInt()
60
- {
61
- return $this->lowerInt;
62
- }
63
-
64
- /**
65
- * Returns the upper value of the IPv4 range as a long integer.
66
- * @return int The upper value of the IPv4 range.
67
- */
68
- public function getUpperInt()
69
- {
70
- return $this->upperInt;
71
- }
72
-
73
- /**
74
- * Returns whether or not a given IP address falls within this range.
75
- * @param string $ipAddress The given IP address.
76
- * @return boolean Returns true if the IP address falls within the range
77
- * and false otherwise.
78
- */
79
- public function containsIp($ipAddress)
80
- {
81
- $ipLong = ip2long($ipAddress);
82
- return ($this->getLowerInt() <= $ipLong) && ($this->getUpperInt() >= $ipLong);
83
- }
84
-
85
- /**
86
- * Computes the lower and upper bounds of the IPv4 range by parsing the
87
- * range string.
88
- * @param string $range The IPv4 range as a string.
89
- */
90
- private function computeLowerAndUpperBounds($range)
91
- {
92
- if (strpos($range, '/') !== false) {
93
- // support CIDR notation
94
- list($this->lowerInt, $this->upperInt) = $this->parseCidrRange($range);
95
- } elseif (strpos($range, '-') !== false) {
96
- // support for IP ranges like '10.0.0.0-10.0.0.255'
97
- list($this->lowerInt, $this->upperInt) = $this->parseHyphenRange($range);
98
- } elseif (($pos = strpos($range, '*')) !== false) {
99
- // support for IP ranges like '10.0.*'
100
- list($this->lowerInt, $this->upperInt) = $this->parseWildcardRange($range, $pos);
101
- } else {
102
- // assume we have a single address
103
- $this->lowerInt = ip2long($range);
104
- $this->upperInt = $this->lowerInt;
105
- }
106
- }
107
-
108
- /**
109
- * Parses a CIDR notation range.
110
- * @param string $range The CIDR range.
111
- * @return array Returns an array with the first element being the lower
112
- * bound of the range and second element being the upper bound.
113
- */
114
- private function parseCidrRange($range)
115
- {
116
- list($address, $mask) = explode('/', $range);
117
- $longAddress = ip2long($address);
118
- return array(
119
- $longAddress & (((1 << $mask) - 1) << (32 - $mask)),
120
- $longAddress | ((1 << (32 - $mask)) - 1)
121
- );
122
- }
123
-
124
- /**
125
- * Parses a hyphen notation range.
126
- * @param string $range The hyphen notation range.
127
- * @return array Returns an array with the first element being the lower
128
- * bound of the range and second element being the upper bound.
129
- */
130
- private function parseHyphenRange($range)
131
- {
132
- return array_map('ip2long', explode('-', $range));
133
- }
134
-
135
- /**
136
- * Parses a wildcard notation range.
137
- * @param string $range The wildcard notation range.
138
- * @param int $pos The integer position of the wildcard within the range string.
139
- * @return array Returns an array with the first element being the lower
140
- * bound of the range and second element being the upper bound.
141
- */
142
- private function parseWildcardRange($range, $pos)
143
- {
144
- $prefix = substr($range, 0, $pos - 1);
145
- $parts = explode('.', $prefix);
146
- $partsCount = 4 - count($parts);
147
- return array(
148
- ip2long(implode('.', array_merge($parts, array_fill(0, $partsCount, 0)))),
149
- ip2long(implode('.', array_merge($parts, array_fill(0, $partsCount, 255))))
150
- );
151
- }
152
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/vendor/vectorface/whip/src/IpRange/Ipv6Range.php DELETED
@@ -1,122 +0,0 @@
1
- <?php
2
-
3
- /*
4
- The MIT License (MIT)
5
-
6
- Copyright (c) 2015 Vectorface, Inc.
7
-
8
- Permission is hereby granted, free of charge, to any person obtaining a copy
9
- of this software and associated documentation files (the "Software"), to deal
10
- in the Software without restriction, including without limitation the rights
11
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
- copies of the Software, and to permit persons to whom the Software is
13
- furnished to do so, subject to the following conditions:
14
-
15
- The above copyright notice and this permission notice shall be included in
16
- all copies or substantial portions of the Software.
17
-
18
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
- THE SOFTWARE.
25
- */
26
-
27
- namespace Vectorface\Whip\IpRange;
28
-
29
- /**
30
- * A class representing an IPv6 address range.
31
- * @copyright Vectorface, Inc 2015
32
- * @author Daniel Bruce <dbruce1126@gmail.com>
33
- */
34
- class Ipv6Range implements IpRange
35
- {
36
-
37
- /** The size of the IPv6 range mask. */
38
- private $mask;
39
-
40
- /** The binary substring of the range minus the mask. */
41
- private $rangeSubstring;
42
-
43
- /**
44
- * Constructor for the class.
45
- * @param string $range The IPv6 range as a string. Supported range styles:
46
- * - CIDR notation (2400:cb00::/32)
47
- * - a specific IP address (::1)
48
- */
49
- public function __construct($range)
50
- {
51
- $this->extractNetworkAndMaskFromRange($range);
52
- }
53
-
54
- /**
55
- * Returns whether or not a given IP address falls within this range.
56
- * @param string $ipAddress The given IP address.
57
- * @return boolean Returns true if the IP address falls within the range
58
- * and false otherwise.
59
- */
60
- public function containsIp($ipAddress)
61
- {
62
- // if the mask is false this means we have a full IP address as a
63
- // range so compare against the whole string
64
- if (false === $this->mask) {
65
- return ($this->rangeSubstring === $this->convertToBinaryString($ipAddress));
66
- }
67
-
68
- // remove the masked part of the address
69
- $ipAddressSubstring = substr(
70
- $this->convertToBinaryString($ipAddress),
71
- 0,
72
- $this->mask
73
- );
74
- return ($this->rangeSubstring === $ipAddressSubstring);
75
- }
76
-
77
- /**
78
- * Extracts the mask and binary string substring of the range to compare
79
- * against incoming IP addresses.
80
- * @param string $range The IPv6 range as a string.
81
- */
82
- private function extractNetworkAndMaskFromRange($range)
83
- {
84
- if (false !== strpos($range, '/')) {
85
- // handle the CIDR notation
86
- list($network, $this->mask) = explode('/', $range);
87
- // store a substring of the binary representation of the range
88
- // minus the masked part
89
- $this->rangeSubstring = substr(
90
- $this->convertToBinaryString($network),
91
- 0,
92
- $this->mask
93
- );
94
- } else {
95
- // handle a single IP address
96
- $this->rangeSubstring = $this->convertToBinaryString($range);
97
- $this->mask = false;
98
- }
99
- }
100
-
101
- /**
102
- * Converts an IPv6 address to a binary string.
103
- * @param string $address The IPv6 address in standard notation.
104
- * @return string Returns the address as a string of bits.
105
- */
106
- private function convertToBinaryString($address)
107
- {
108
- return implode('', array_map(
109
- array(__CLASS__, 'hexToBinary'),
110
- str_split(bin2hex(inet_pton($address)))
111
- ));
112
- }
113
- /**
114
- * Converts a hexadecimal character to a 4-digit binary string.
115
- * @param string $hex The hexadecimal character.
116
- * @return string Returns a 4-digit binary string.
117
- */
118
- private static function hexToBinary($hex)
119
- {
120
- return str_pad(base_convert($hex, 16, 2), 4, '0', STR_PAD_LEFT);
121
- }
122
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/vendor/vectorface/whip/src/Request/Psr7RequestAdapter.php DELETED
@@ -1,74 +0,0 @@
1
- <?php
2
-
3
- /*
4
- The MIT License (MIT)
5
-
6
- Copyright (c) 2015 Vectorface, Inc.
7
-
8
- Permission is hereby granted, free of charge, to any person obtaining a copy
9
- of this software and associated documentation files (the "Software"), to deal
10
- in the Software without restriction, including without limitation the rights
11
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
- copies of the Software, and to permit persons to whom the Software is
13
- furnished to do so, subject to the following conditions:
14
-
15
- The above copyright notice and this permission notice shall be included in
16
- all copies or substantial portions of the Software.
17
-
18
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
- THE SOFTWARE.
25
- */
26
-
27
- namespace Vectorface\Whip\Request;
28
-
29
- /**
30
- * Provide IP address data from ta PSR-7 request.
31
- */
32
- class Psr7RequestAdapter implements RequestAdapter
33
- {
34
- /**
35
- * The PSR-7 request that serves as the source of data.
36
- *
37
- * @var \Psr\Http\Message\ServerRequestInterface
38
- */
39
- private $request;
40
-
41
- /**
42
- * A formatted version of the HTTP headers: ["header" => "value", ...]
43
- *
44
- * @var string[]
45
- */
46
- private $headers;
47
-
48
- /**
49
- * Create a new adapter for a superglobal $_SERVER-style array.
50
- *
51
- * @param \Psr\Http\Message\ServerRequestInterface $request An array in a format like PHP's $_SERVER var.
52
- */
53
- public function __construct(\Psr\Http\Message\ServerRequestInterface $request)
54
- {
55
- $this->request = $request;
56
- }
57
-
58
- public function getRemoteAddr()
59
- {
60
- $server = $this->request->getServerParams();
61
- return isset($server['REMOTE_ADDR']) ? $server['REMOTE_ADDR'] : null;
62
- }
63
-
64
- public function getHeaders()
65
- {
66
- if (!isset($this->headers)) {
67
- $this->headers = array();
68
- foreach ($this->request->getHeaders() as $header => $values) {
69
- $this->headers[strtolower($header)] = end($values);
70
- }
71
- }
72
- return $this->headers;
73
- }
74
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/vendor/vectorface/whip/src/Request/RequestAdapter.php DELETED
@@ -1,47 +0,0 @@
1
- <?php
2
-
3
- /*
4
- The MIT License (MIT)
5
-
6
- Copyright (c) 2015 Vectorface, Inc.
7
-
8
- Permission is hereby granted, free of charge, to any person obtaining a copy
9
- of this software and associated documentation files (the "Software"), to deal
10
- in the Software without restriction, including without limitation the rights
11
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
- copies of the Software, and to permit persons to whom the Software is
13
- furnished to do so, subject to the following conditions:
14
-
15
- The above copyright notice and this permission notice shall be included in
16
- all copies or substantial portions of the Software.
17
-
18
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
- THE SOFTWARE.
25
- */
26
-
27
- namespace Vectorface\Whip\Request;
28
-
29
- /**
30
- * RequestAdapter: Interface for different request formats.
31
- */
32
- interface RequestAdapter
33
- {
34
- /**
35
- * Get the remote address, as seen by this request format.
36
- *
37
- * @return string The remote address. IPv4 or IPv6, as applicable.
38
- */
39
- public function getRemoteAddr();
40
-
41
- /**
42
- * Get a key/value mapping of request headers, keys in lowercase.
43
- *
44
- * @return string[] An associative array mapping headers to values.
45
- */
46
- public function getHeaders();
47
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/vendor/vectorface/whip/src/Request/SuperglobalRequestAdapter.php DELETED
@@ -1,88 +0,0 @@
1
- <?php
2
-
3
- /*
4
- The MIT License (MIT)
5
-
6
- Copyright (c) 2015 Vectorface, Inc.
7
-
8
- Permission is hereby granted, free of charge, to any person obtaining a copy
9
- of this software and associated documentation files (the "Software"), to deal
10
- in the Software without restriction, including without limitation the rights
11
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
- copies of the Software, and to permit persons to whom the Software is
13
- furnished to do so, subject to the following conditions:
14
-
15
- The above copyright notice and this permission notice shall be included in
16
- all copies or substantial portions of the Software.
17
-
18
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
- THE SOFTWARE.
25
- */
26
-
27
- namespace Vectorface\Whip\Request;
28
-
29
- /**
30
- * Provide IP address data from the $_SERVER superglobal.
31
- */
32
- class SuperglobalRequestAdapter implements RequestAdapter
33
- {
34
- /**
35
- * The $_SERVER-style array that serves as the source of data.
36
- *
37
- * @var string[]
38
- */
39
- private $server;
40
-
41
- /**
42
- * A formatted version of the HTTP headers: ["header" => "value", ...]
43
- *
44
- * @var string[]
45
- */
46
- private $headers;
47
-
48
- /**
49
- * Create a new adapter for a superglobal $_SERVER-style array.
50
- *
51
- * @param string[] $server An array in a format like PHP's $_SERVER var.
52
- */
53
- public function __construct(array $server)
54
- {
55
- $this->server = $server;
56
- }
57
-
58
- public function getRemoteAddr()
59
- {
60
- return isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : null;
61
- }
62
-
63
- public function getHeaders()
64
- {
65
- if (!isset($this->headers)) {
66
- $this->headers = $this->serverToHeaders($this->server);
67
- }
68
- return $this->headers;
69
- }
70
-
71
- /**
72
- * Convert from $_SERVER-style format to normal header names.
73
- *
74
- * @param string[] $server The $_SERVER-style array.
75
- * @return string[] Array of headers with lowercased keys.
76
- */
77
- private static function serverToHeaders(array $server)
78
- {
79
- $headers = array();
80
- foreach ($server as $key => $value) {
81
- if (strpos($key, 'HTTP_') === 0) {
82
- $key = strtolower(str_replace("_", '-', substr($key, 5)));
83
- $headers[$key] = $value;
84
- }
85
- }
86
- return $headers;
87
- }
88
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/vendor/vectorface/whip/src/Whip.php DELETED
@@ -1,285 +0,0 @@
1
- <?php
2
-
3
- /*
4
- The MIT License (MIT)
5
-
6
- Copyright (c) 2015 Vectorface, Inc.
7
-
8
- Permission is hereby granted, free of charge, to any person obtaining a copy
9
- of this software and associated documentation files (the "Software"), to deal
10
- in the Software without restriction, including without limitation the rights
11
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
- copies of the Software, and to permit persons to whom the Software is
13
- furnished to do so, subject to the following conditions:
14
-
15
- The above copyright notice and this permission notice shall be included in
16
- all copies or substantial portions of the Software.
17
-
18
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
- THE SOFTWARE.
25
- */
26
-
27
- namespace Vectorface\Whip;
28
-
29
- use \Exception;
30
- use Vectorface\Whip\IpRange\IpWhitelist;
31
- use Vectorface\Whip\Request\RequestAdapter;
32
- use Vectorface\Whip\Request\Psr7RequestAdapter;
33
- use Vectorface\Whip\Request\SuperglobalRequestAdapter;
34
- use Psr\Http\Message\ServerRequestInterface;
35
-
36
- /**
37
- * A class for accurately looking up a client's IP address.
38
- * This class checks a call time configurable list of headers in the $_SERVER
39
- * superglobal to determine the client's IP address.
40
- * @copyright Vectorface, Inc 2015
41
- * @author Daniel Bruce <dbruce1126@gmail.com>
42
- * @author Cory Darby <ckdarby@vectorface.com>
43
- */
44
- class Whip
45
- {
46
- /** The whitelist key for IPv4 addresses */
47
- const IPV4 = IpWhitelist::IPV4;
48
-
49
- /** The whitelist key for IPv6 addresses */
50
- const IPV6 = IpWhitelist::IPV6;
51
-
52
- /** Indicates all header methods will be used. */
53
- const ALL_METHODS = 255;
54
- /** Indicates the REMOTE_ADDR method will be used. */
55
- const REMOTE_ADDR = 1;
56
- /** Indicates a set of possible proxy headers will be used. */
57
- const PROXY_HEADERS = 2;
58
- /** Indicates any CloudFlare specific headers will be used. */
59
- const CLOUDFLARE_HEADERS = 4;
60
- /** Indicates any Incapsula specific headers will be used. */
61
- const INCAPSULA_HEADERS = 8;
62
- /** Indicates custom listed headers will be used. */
63
- const CUSTOM_HEADERS = 128;
64
-
65
- /** The array of mapped header strings. */
66
- private static $headers = array(
67
- self::CUSTOM_HEADERS => array(),
68
- self::INCAPSULA_HEADERS => array(
69
- 'incap-client-ip'
70
- ),
71
- self::CLOUDFLARE_HEADERS => array(
72
- 'cf-connecting-ip'
73
- ),
74
- self::PROXY_HEADERS => array(
75
- 'client-ip',
76
- 'x-forwarded-for',
77
- 'x-forwarded',
78
- 'x-cluster-client-ip',
79
- 'forwarded-for',
80
- 'forwarded',
81
- 'x-real-ip',
82
- ),
83
- );
84
-
85
- /** the bitmask of enabled methods */
86
- private $enabled;
87
-
88
- /** the array of IP whitelist ranges to check against */
89
- private $whitelist;
90
-
91
- /**
92
- * An object holding the source of addresses we will check
93
- *
94
- * @var RequestAdapter
95
- */
96
- private $source;
97
-
98
- /**
99
- * Constructor for the class.
100
- * @param int $enabled The bitmask of enabled headers.
101
- * @param array $whitelists The array of IP ranges to be whitelisted.
102
- * @param mixed $source A supported source of IP data.
103
- */
104
- public function __construct($enabled = self::ALL_METHODS, array $whitelists = array(), $source = null)
105
- {
106
- $this->enabled = (int) $enabled;
107
- if (isset($source)) {
108
- $this->setSource($source);
109
- }
110
- $this->whitelist = array();
111
- foreach ($whitelists as $header => $ipRanges) {
112
- $header = $this->normalizeHeaderName($header);
113
- $this->whitelist[$header] = new IpWhitelist($ipRanges);
114
- }
115
- }
116
-
117
- /**
118
- * Adds a custom header to the list.
119
- * @param string $header The custom header to add.
120
- * @return Whip Returns $this.
121
- */
122
- public function addCustomHeader($header)
123
- {
124
- self::$headers[self::CUSTOM_HEADERS][] = $this->normalizeHeaderName($header);
125
- return $this;
126
- }
127
-
128
- /**
129
- * Sets the source data used to lookup the addresses.
130
- *
131
- * @param $source The source array.
132
- * @return Whip Returns $this.
133
- */
134
- public function setSource($source)
135
- {
136
- $this->source = $this->getRequestAdapter($source);
137
-
138
- return $this;
139
- }
140
-
141
- /**
142
- * Returns the IP address of the client using the given methods.
143
- * @param mixed $source (optional) The source data. If omitted, the class
144
- * will use the value passed to Whip::setSource or fallback to
145
- * $_SERVER.
146
- * @return string Returns the IP address as a string or false if no
147
- * IP address could be found.
148
- */
149
- public function getIpAddress($source = null)
150
- {
151
- $source = $this->getRequestAdapter($this->coalesceSources($source));
152
- $remoteAddr = $source->getRemoteAddr();
153
- $requestHeaders = $source->getHeaders();
154
-
155
- foreach (self::$headers as $key => $headers) {
156
- if (!$this->isMethodUsable($key, $remoteAddr)) {
157
- continue;
158
- }
159
-
160
- if ($ipAddress = $this->extractAddressFromHeaders($requestHeaders, $headers)) {
161
- return $ipAddress;
162
- }
163
- }
164
-
165
- if ($remoteAddr && ($this->enabled & self::REMOTE_ADDR)) {
166
- return $remoteAddr;
167
- }
168
-
169
- return false;
170
- }
171
-
172
- /**
173
- * Returns the valid IP address or false if no valid IP address was found.
174
- * @param mixed $source (optional) The source data. If omitted, the class
175
- * will use the value passed to Whip::setSource or fallback to
176
- * $_SERVER.
177
- * @return string|false Returns the IP address (as a string) of the client or false
178
- * if no valid IP address was found.
179
- */
180
- public function getValidIpAddress($source = null)
181
- {
182
- $ipAddress = $this->getIpAddress($source);
183
- if (false === $ipAddress || false === @inet_pton($ipAddress)) {
184
- return false;
185
- }
186
- return $ipAddress;
187
- }
188
-
189
- /**
190
- * Normalizes HTTP header name representations.
191
- *
192
- * HTTP_MY_HEADER and My-Header would be transformed to my-header.
193
- *
194
- * @param string $header The original header name.
195
- * @return string The normalized header name.
196
- */
197
- private function normalizeHeaderName($header)
198
- {
199
- if (strpos($header, 'HTTP_') === 0) {
200
- $header = str_replace('_', '-', substr($header, 5));
201
- }
202
- return strtolower($header);
203
- }
204
-
205
- /**
206
- * Finds the first element in $headers that is present in $_SERVER and
207
- * returns the IP address mapped to that value.
208
- * If the IP address is a list of comma separated values, the last value
209
- * in the list will be returned.
210
- * If no IP address is found, we return false.
211
- * @param array $requestHeaders The request headers to pull data from.
212
- * @param array $headers The list of headers to check.
213
- * @return string|false Returns the IP address as a string or false if no IP
214
- * IP address was found.
215
- */
216
- private function extractAddressFromHeaders($requestHeaders, $headers)
217
- {
218
- foreach ($headers as $header) {
219
- if (!empty($requestHeaders[$header])) {
220
- $list = explode(',', $requestHeaders[$header]);
221
- return trim(end($list));
222
- }
223
- }
224
- return false;
225
- }
226
-
227
- /**
228
- * Returns whether or not the given method is enabled and usable.
229
- *
230
- * This method checks if the method is enabled and whether the method's data
231
- * is usable given it's IP whitelist.
232
- *
233
- * @param string $key The source key.
234
- * @param string $ipAddress The IP address.
235
- * @return boolean Returns true if the IP address is whitelisted and false
236
- * otherwise. Returns true if the source does not have a whitelist
237
- * specified.
238
- */
239
- private function isMethodUsable($key, $ipAddress)
240
- {
241
- if (!($key & $this->enabled)) {
242
- return false;
243
- }
244
- if (!isset($this->whitelist[$key])) {
245
- return true;
246
- }
247
- return $this->whitelist[$key]->isIpWhitelisted($ipAddress);
248
- }
249
-
250
- /**
251
- * Get a source/request adapter for a given source of IP data.
252
- *
253
- * @param mixed $source A supported source of request data.
254
- * @return RequestAdapter A RequestAdapter implementation for the given source.
255
- */
256
- private function getRequestAdapter($source)
257
- {
258
- if ($source instanceof RequestAdapter) {
259
- return $source;
260
- } elseif ($source instanceof ServerRequestInterface) {
261
- return new Psr7RequestAdapter($source);
262
- } elseif (is_array($source)) {
263
- return new SuperglobalRequestAdapter($source);
264
- }
265
-
266
- throw new \InvalidArgumentException("Unknown IP source.");
267
- }
268
-
269
- /**
270
- * Given available sources, get the first available source of IP data.
271
- *
272
- * @param mixed $source A source data argument, if available.
273
- * @return mixed The best available source, after fallbacks.
274
- */
275
- private function coalesceSources($source = null)
276
- {
277
- if (isset($source)) {
278
- return $source;
279
- } elseif (isset($this->source)) {
280
- return $this->source;
281
- }
282
-
283
- return $_SERVER;
284
- }
285
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/vendor/vectorface/whip/tests/WhipTest.php DELETED
@@ -1,459 +0,0 @@
1
- <?php
2
- /*
3
- The MIT License (MIT)
4
-
5
- Copyright (c) 2015 Vectorface, Inc.
6
-
7
- Permission is hereby granted, free of charge, to any person obtaining a copy
8
- of this software and associated documentation files (the "Software"), to deal
9
- in the Software without restriction, including without limitation the rights
10
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- copies of the Software, and to permit persons to whom the Software is
12
- furnished to do so, subject to the following conditions:
13
-
14
- The above copyright notice and this permission notice shall be included in
15
- all copies or substantial portions of the Software.
16
-
17
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- THE SOFTWARE.
24
- */
25
- namespace Vectorface\WhipTests;
26
-
27
- use PHPUnit_Framework_TestCase;
28
- use Vectorface\Whip\Whip;
29
- use Psr\Http\Message\ServerRequestInterface;
30
-
31
- /**
32
- * Test class for testing Whip.
33
- * @backupGlobals enabled
34
- * @copyright Vectorface, Inc 2015
35
- * @author Daniel Bruce <dbruce@vectorface.com>
36
- */
37
- class WhipTest extends PHPUnit_Framework_TestCase
38
- {
39
- /**
40
- * Tests that an invalid source format is rejected.
41
- * @expectedException \InvalidArgumentException
42
- */
43
- public function testInvalidSource()
44
- {
45
- new Whip(Whip::REMOTE_ADDR, array(), new \stdClass());
46
- }
47
- /**
48
- * Tests that we get back the right IP when there using superglobals.
49
- */
50
- public function testSuperglobal()
51
- {
52
- $_SERVER = array('REMOTE_ADDR' => '24.24.24.24');
53
- $lookup = new Whip(Whip::REMOTE_ADDR);
54
- $this->assertEquals('24.24.24.24', $lookup->getValidIpAddress());
55
- }
56
-
57
- /**
58
- * Tests that we get back 127.0.0.1 when there is no superglobal information
59
- * at all.
60
- */
61
- public function testEmptySuperglobal()
62
- {
63
- $_SERVER = array();
64
- $lookup = new Whip();
65
- $this->assertFalse($lookup->getIpAddress());
66
- }
67
-
68
- /**
69
- * Helper to get a mocked PSR-7 instance.
70
- *
71
- * @param string $remoteAddr The remote address to mock.
72
- * @param string[][] $headers The headers, in the format expected by Psr-7.
73
- */
74
- private function getHttpMessageMock($remoteAddr, array $headers = array())
75
- {
76
- $stub = $this->getMockBuilder("Psr\Http\Message\ServerRequestInterface")
77
- ->getMock();
78
-
79
- $stub->method('getServerParams')
80
- ->willReturn(array('REMOTE_ADDR' => $remoteAddr));
81
- $stub->method('getHeaders')
82
- ->willReturn($headers);
83
-
84
- return $stub;
85
- }
86
- /**
87
- * Tests that we can use a PSR-7 ServerRequestInterface compatible class.
88
- */
89
- public function testPsr7Request()
90
- {
91
- $lookup = new Whip(
92
- Whip::PROXY_HEADERS,
93
- array(
94
- Whip::PROXY_HEADERS => array(
95
- Whip::IPV4 => array(
96
- '127.0.0.1'
97
- )
98
- )
99
- ),
100
- $this->getHttpMessageMock("127.0.0.1", array('X-Forwarded-For' => array('192.168.1.1,32.32.32.32')))
101
- );
102
-
103
- $this->assertEquals('32.32.32.32', $lookup->getIpAddress());
104
- }
105
-
106
- /**
107
- * Tests that we get false when no valid IP address could be found.
108
- */
109
- public function testNoAddresFoundDueToBitmask()
110
- {
111
- $lookup = new Whip(Whip::PROXY_HEADERS);
112
- $lookup->setSource(array('REMOTE_ADDR' => '127.0.0.1'));
113
- $this->assertFalse($lookup->getIpAddress());
114
- }
115
-
116
- /**
117
- * Tests the standard REMOTE_ADDR method.
118
- */
119
- public function testRemoteAddrMethod()
120
- {
121
- $lookup = new Whip(Whip::REMOTE_ADDR);
122
- $lookup->setSource(array('REMOTE_ADDR' => '24.24.24.24'));
123
- $this->assertEquals('24.24.24.24', $lookup->getValidIpAddress());
124
- }
125
-
126
- /**
127
- * Tests that an invalid IPv4 address returns false.
128
- */
129
- public function testInvalidIPv4Address()
130
- {
131
- $lookup = new Whip(Whip::REMOTE_ADDR);
132
- $lookup->setSource(array('REMOTE_ADDR' => '127.0.0.256'));
133
- $this->assertFalse($lookup->getValidIpAddress());
134
- }
135
-
136
- /**
137
- * Tests a valid IPv6 address.
138
- */
139
- public function testValidIPv6Address()
140
- {
141
- $lookup = new Whip(Whip::REMOTE_ADDR);
142
- $lookup->setSource(array('REMOTE_ADDR' => '::1'));
143
- $this->assertEquals('::1', $lookup->getValidIpAddress());
144
- }
145
-
146
- /**
147
- * Tests that we accept whitelisted proxy methods when the IP matches, even
148
- * if the IP listed is a comma separated list.
149
- *
150
- * @dataProvider proxyMethodWhitelistProvider
151
- */
152
- public function testValidWhitelistedProxyMethod($remoteAddr)
153
- {
154
- $lookup = new Whip(
155
- Whip::PROXY_HEADERS,
156
- array(
157
- Whip::PROXY_HEADERS => array(
158
- Whip::IPV4 => array('127.0.0.1'),
159
- Whip::IPV6 => array('::1')
160
- )
161
- ),
162
- array(
163
- 'REMOTE_ADDR' => $remoteAddr,
164
- 'HTTP_X_FORWARDED_FOR' => '192.168.1.1,32.32.32.32'
165
- )
166
- );
167
- $this->assertEquals('32.32.32.32', $lookup->getIpAddress());
168
- }
169
-
170
- /**
171
- * Repeats the above test twice for ipv4 and ipv6
172
- */
173
- public function proxyMethodWhitelistProvider()
174
- {
175
- return array(
176
- array('127.0.0.1'),
177
- array('::1'),
178
- );
179
- }
180
-
181
- /**
182
- * Tests that we accept proxy method based on a whitelisted IP using the
183
- * dashed range notation.
184
- */
185
- public function testValidWhitelistedProxyMethodWithDashNotation()
186
- {
187
- $lookup = new Whip(
188
- Whip::PROXY_HEADERS,
189
- array(
190
- Whip::PROXY_HEADERS => array(
191
- Whip::IPV4 => array(
192
- '127.0.0.0-127.0.255.255',
193
- ),
194
- Whip::IPV6 => array(
195
- '::1'
196
- )
197
- )
198
- ),
199
- array(
200
- 'REMOTE_ADDR' => '127.0.0.1',
201
- 'HTTP_X_FORWARDED_FOR' => '32.32.32.32'
202
- )
203
- );
204
- $this->assertEquals('32.32.32.32', $lookup->getIpAddress());
205
- }
206
-
207
- /**
208
- * Tests that we accept proxy method based on a whitelisted IP using the
209
- * wildcard asterix notation.
210
- */
211
- public function testValidWhitelistedProxyMethodWithWildcardNotation()
212
- {
213
- $lookup = new Whip(
214
- Whip::PROXY_HEADERS,
215
- array(
216
- Whip::PROXY_HEADERS => array(
217
- Whip::IPV4 => array(
218
- '127.0.*'
219
- ),
220
- Whip::IPV6 => array(
221
- '::1'
222
- )
223
- )
224
- ),
225
- array(
226
- 'REMOTE_ADDR' => '127.0.0.1',
227
- 'HTTP_X_FORWARDED_FOR' => '32.32.32.32'
228
- )
229
- );
230
- $this->assertEquals('32.32.32.32', $lookup->getIpAddress());
231
- }
232
-
233
- /**
234
- * Tests that we accept proxy method based on a whitelisted IP using the
235
- * CIDR address notation.
236
- */
237
- public function testValidWhitelistedProxyMethodWithCIDRdNotation()
238
- {
239
- $lookup = new Whip(
240
- Whip::PROXY_HEADERS,
241
- array(
242
- Whip::PROXY_HEADERS => array(
243
- Whip::IPV4 => array(
244
- '127.0.0.0/24'
245
- ),
246
- Whip::IPV6 => array(
247
- '::1'
248
- )
249
- )
250
- ),
251
- array(
252
- 'REMOTE_ADDR' => '127.0.0.1',
253
- 'HTTP_X_FORWARDED_FOR' => '32.32.32.32'
254
- )
255
- );
256
- $this->assertEquals('32.32.32.32', $lookup->getIpAddress());
257
- }
258
-
259
- /**
260
- * Tests that we get false if there is a valid IP in a proxy header but
261
- * we reject it due to REMOTE_ADDR not being in the whitelist.
262
- */
263
- public function testValidIpRejectedDueToWhitelist()
264
- {
265
- $lookup = new Whip(
266
- Whip::PROXY_HEADERS,
267
- array(
268
- Whip::PROXY_HEADERS => array(
269
- Whip::IPV4 => array(
270
- '127.0.0.1/24'
271
- ),
272
- Whip::IPV6 => array(
273
- '::1'
274
- )
275
- )
276
- ),
277
- array(
278
- 'REMOTE_ADDR' => '24.24.24.24',
279
- 'HTTP_X_FORWARDED_FOR' => '32.32.32.32'
280
- )
281
- );
282
- $this->assertFalse($lookup->getIpAddress());
283
- }
284
-
285
- /**
286
- * Tests that we reject a proxy listed IPv6 address that does not fall within
287
- * the allowed subnet.
288
- */
289
- public function testIPv6AddressRejectedDueToWhitelist()
290
- {
291
- $lookup = new Whip(
292
- Whip::PROXY_HEADERS,
293
- array(
294
- Whip::PROXY_HEADERS => array(
295
- Whip::IPV6 => array(
296
- '2400:cb00::/32'
297
- )
298
- )
299
- ),
300
- array(
301
- 'REMOTE_ADDR' => '::1',
302
- 'HTTP_X_FORWARDED_FOR' => '::1'
303
- )
304
- );
305
- $this->assertFalse($lookup->getIpAddress());
306
- }
307
-
308
- /**
309
- * Tests that we reject a proxy listed IPv6 address that does not fall within
310
- * the allowed subnet.
311
- */
312
- public function testIPv6AddressFoundInWhitelist()
313
- {
314
- $lookup = new Whip(
315
- Whip::PROXY_HEADERS,
316
- array(
317
- Whip::PROXY_HEADERS => array(
318
- Whip::IPV6 => array(
319
- '::1/32'
320
- )
321
- )
322
- ),
323
- array(
324
- 'REMOTE_ADDR' => '::1',
325
- 'HTTP_X_FORWARDED_FOR' => '::1'
326
- )
327
- );
328
- $this->assertEquals('::1', $lookup->getIpAddress());
329
- }
330
-
331
- /**
332
- * Test that an IPv4 address is rejected because the whitelist is empty for
333
- * IPv4.
334
- */
335
- public function testIPv4AddressRejectedDueToEmptyWhitelist()
336
- {
337
- $lookup = new Whip(
338
- Whip::PROXY_HEADERS,
339
- array(
340
- Whip::PROXY_HEADERS => array(
341
- Whip::IPV6 => array(
342
- '::1/32'
343
- )
344
- )
345
- ),
346
- array(
347
- 'REMOTE_ADDR' => '127.0.0.1',
348
- 'HTTP_X_FORWARDED_FOR' => '24.24.24.24'
349
- )
350
- );
351
- $this->assertFalse($lookup->getIpAddress());
352
- }
353
-
354
- /**
355
- * Test that an IPv6 address is rejected because the whitelist is empty for
356
- * IPv6.
357
- */
358
- public function testIPv6AddressRejectedDueToEmptyWhitelist()
359
- {
360
- $lookup = new Whip(
361
- Whip::PROXY_HEADERS,
362
- array(
363
- Whip::PROXY_HEADERS => array(
364
- Whip::IPV4 => array(
365
- '127.0.0.0/24'
366
- )
367
- )
368
- ),
369
- array(
370
- 'REMOTE_ADDR' => '::1',
371
- 'HTTP_X_FORWARDED_FOR' => '::1'
372
- )
373
- );
374
- $this->assertFalse($lookup->getIpAddress());
375
- }
376
-
377
- /**
378
- * Test a custom header with a whitelisted IP.
379
- */
380
- public function testCustomHeader()
381
- {
382
- $lookup = new Whip(
383
- Whip::CUSTOM_HEADERS | Whip::REMOTE_ADDR,
384
- array(
385
- Whip::CUSTOM_HEADERS => array(
386
- Whip::IPV4 => array(
387
- '127.0.0.1',
388
- '::1'
389
- )
390
- )
391
- ),
392
- array(
393
- 'REMOTE_ADDR' => '127.0.0.1',
394
- 'HTTP_CUSTOM_SECRET_HEADER' => '32.32.32.32'
395
- )
396
- );
397
- $this->assertEquals(
398
- '32.32.32.32',
399
- $lookup->addCustomHeader('HTTP_CUSTOM_SECRET_HEADER')->getIpAddress()
400
- );
401
- }
402
-
403
- /**
404
- * Test HTTP_X_REAL_IP header.
405
- */
406
- public function testHttpXRealIpHeader()
407
- {
408
- $lookup = new Whip(
409
- Whip::PROXY_HEADERS | Whip::REMOTE_ADDR,
410
- array(),
411
- array(
412
- 'REMOTE_ADDR' => '127.0.0.1',
413
- 'HTTP_X_REAL_IP' => '24.24.24.24'
414
- )
415
- );
416
- $this->assertEquals('24.24.24.24', $lookup->getIpAddress());
417
- }
418
-
419
- /**
420
- * Tests that if we specify the source array, it overrides any values found
421
- * in the $_SERVER array.
422
- */
423
- public function testSourceArrayOverridesServerSuperglobal()
424
- {
425
- $source = array(
426
- 'REMOTE_ADDR' => '24.24.24.24'
427
- );
428
- $lookup = new Whip(Whip::REMOTE_ADDR, array(), array('REMOTE_ADDR' => '127.0.0.1'));
429
- $this->assertNotEquals($source['REMOTE_ADDR'], $lookup->getIpAddress());
430
- $this->assertEquals($source['REMOTE_ADDR'], $lookup->getIpAddress($source));
431
- }
432
-
433
- /**
434
- * Tests that if we specify the source array through Whip::setSource, the
435
- * class will override any values found in $_SERVER.
436
- */
437
- public function testSetSourceArrayOverridesServerSuperglobal()
438
- {
439
- $source = array(
440
- 'REMOTE_ADDR' => '24.24.24.24'
441
- );
442
- $lookup = new Whip(Whip::REMOTE_ADDR, array(), array('REMOTE_ADDR' => '127.0.0.1'));
443
- $this->assertNotEquals($source['REMOTE_ADDR'], $lookup->getIpAddress());
444
- $lookup->setSource($source);
445
- $this->assertEquals($source['REMOTE_ADDR'], $lookup->getIpAddress());
446
- }
447
-
448
- /**
449
- * Tests that we fallback to REMOTE_ADDR if the custom header was not found
450
- */
451
- public function testFallbackToRemoteAddr()
452
- {
453
- $source = array(
454
- 'REMOTE_ADDR' => '24.24.24.24'
455
- );
456
- $lookup = new Whip(Whip::PROXY_HEADERS | Whip::REMOTE_ADDR, array(), $source);
457
- $this->assertEquals($source['REMOTE_ADDR'], $lookup->getIpAddress());
458
- }
459
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/vendor/vectorface/whip/tests/bootstrap.php DELETED
@@ -1,3 +0,0 @@
1
- <?php
2
-
3
- require_once __DIR__.'/../vendor/autoload.php';
 
 
 
older-changelog.txt DELETED
@@ -1,888 +0,0 @@
1
- == Changelog ==
2
- = 10.0 =
3
- * Release Date: January 15, 2016
4
- * Added: Widgets now support reloading on overview and dashboard screen.
5
- * Updated: Overview screen now loads widgets dynamically to reduce memory usage.
6
- * Updated: Dashboard widgets now load dynamically.
7
- * Updated: Enabling dashboard widgets now no longer require a page load to display the contents.
8
- * Updated: Replaced the old eye icon and "more..." link on the right of the title on the overview widgets with a new icon on the right beside the open/close icon.
9
- * Fixed: Removed extraneous single quote in SQL statement on referrers page, thanks jhertel.
10
- * Fixed: Order of parameters in referrers page when viewing individual referrers was incorrect and resulted in a blank list.
11
- * Fixed: UpdatedSQL for last post date detection to order by post_date instead of ID as someone could enter a date in the past for their publish date. Thanks PC1271 for the fix.
12
- * Fixed: The referrers widget would only select the first 100k records due to a limit in PHP/MySQL, it will now select all records.
13
- * Removed: Widget selection and ordering from the settings page, the "Screen Options" tab can now be used on the enabled/disable widgets and drag and drop will remember their location.
14
- * Removed: Overview page memory usage in the optimization page as it is no longer relevant.
15
-
16
- = 9.7 =
17
- * Release Date: December 30, 2015
18
- * Added: A date range to the referrers page.
19
- * Added: A Date range selector to browsers page.
20
- * Updated: General SQL cleanups.
21
- * Updated: browscap library to 2.1.1.
22
- * Updated: GeoIP library to 2.3.3.
23
- * Updated: phpUserAgent library to 0.5
24
-
25
- = 9.6.6 =
26
- * Release Date: November 1, 2015
27
- * Updated: Use timezone corrected dates for date pickers.
28
- * Updated the get_ip code to return 127.0.0.1 if no IP address is found (can happen when a user runs WordPress from a command line function, like when setting up a scheduled cron job).
29
- * Fixed: Several security related updates, thanks CodeV.
30
-
31
- = 9.6.5 =
32
- * Release Date: September 18, 2015
33
- * Updated: Updated support libraries, including browscap (2.0.5) and GeoIP (webservices).
34
- * Updated: The hits column in the post/pages list no longer requires manage permissions but instead view permission.
35
- * Fixed: New browscap.ini format was causing fatal errors in certain circumstances.
36
- * Fixed: Missing close tag on the summary widget's users online link.
37
- * Fixed: When purging data an incorrect column name was used when updating the historical table.
38
-
39
- = 9.6.4 =
40
- * Release Date: September 15, 2015
41
- * Updated: Support new browscap.ini file format.
42
-
43
- = 9.6.3 =
44
- * Release Date: September 11, 2015
45
- * Updated: The database update nag link to the optimization page instead of the settings page.
46
- * Updated: Handle the case where the downloads haven't happened yet.
47
- * Fixed: In some cases the extenrals tab would show the wrong date for the next scheduled update.
48
- * Fixed: In some cases the Piwik and other features may not be enabled even when the checkboxes were selected.
49
- * Fixed: If no page id was passed in on the pagestats shortcode the wrong default for page id would be used and no stats would be displayed.
50
-
51
- = 9.6.2 =
52
- * Release Date: September 5, 2015
53
- * Added: Search table to the empty table list.
54
- * Added: Search table size to the optimization page.
55
- * Added: Updated SQL calls to the pages table to use $wpdb->prepare() to protect against SQL inject attacks.
56
- * Fixed: Check of $wp_roles type as it is an object and not an array which caused only admins to be able to view the statistics.
57
- * Fixed: Top referring only displayed search engines.
58
- * Updated: Layout of the maintenance tab.
59
-
60
- = 9.6.1 =
61
- * Release Date: September 4, 2015
62
- * Fixed: Error with undeclared global $WP_Statistics when updating the database.
63
- * Added: Re-validation of the current database updates required when loading the optimization page.
64
-
65
- = 9.6 =
66
- * Release Date: September 3, 2015
67
- * Added: New admin notices for if the database requires updates.
68
- * Added: Page/post id field to pagestats shortcode.
69
- * Added: Ask.com to search engine list, disabled by default.
70
- * Fixed: Display of the dashboard referrers widget.
71
- * Fixed: incorrect table name when dropping the old 'AString' field.
72
- * Fixed: Error message if the global $wp_roles hadn't been set when we accessed it.
73
- * Fixed: When exporting, no data was exported.
74
- * Fixed: When excluding countries, multiple entries would not be parsed correctly.
75
- * Updated: Purging code now includes the search table.
76
- * Updated: Search conversion code to limit the number of records retreived to 10000 and then loop through them to ensure we don't run out of memory during the conversion process.
77
- * Updated: Cleaned up the admin notices code.
78
- * Updated: Persian translation. Thanks Ali Zeinali.
79
-
80
- = 9.5.3 =
81
- * Release Date: August 19, 2015
82
- * Added: More robust error reporting if a plugin table is missing.
83
- * Added: Support to export the search table.
84
- * Fixed: The install script for older versions of MySQL (5.0.x).
85
- * Fixed: Export script no longer generates errors when exporting an empty table.
86
- * Fixed: WP_Debug error on $crawler when it was an object but didn't have the right properties (aka wasn't the right object).
87
- * Fixed: Sidebar widget works again in WordPress 4.3.
88
-
89
- = 9.5.2 =
90
- * Release Date: August 8, 2015
91
- * Fixed: XSS issue with top-referrers page, thanks Swift Security (http://swiftsecurity.swte.ch/).
92
- * Updated: If the GeoIP code is disabled, the warning message was pointing to the old GeoIP tab instead of the new Externals tab.
93
- * Updated: French translation.
94
-
95
- = 9.5.1 =
96
- * Release Date: August 4, 2015
97
- * Fixed: Issue with verifying the WP Statistics tables exist on databases with hyphens in their names.
98
- * Updated: Arabic translation.
99
-
100
- = 9.5 =
101
- * Release Date: August 3, 2015
102
- * Added: Referrer Spam exclusions using the Piwik Referrer Spam Blacklist (see Statistics->Settings->Externals to enable).
103
- * Added: Code to remove 'AString' column if it exists in the visitors table during upgrades (bug in a older previous version of WP Statistics erroneously created it).
104
- * Fixed: Duplicate key name warning during upgrades for 'date_ip_agent' index.
105
- * Fixed: Warning on 'date_ip' index does not exist when trying to drop it during upgrades.
106
- * Updated: Storing of search engine/words data is now in it's own table for better performance.
107
- * Updated: Combined the GeoIP and browscap tabs in settings in to the Externals tab.
108
- * Updated: GeoIP library to V 2.3.1.
109
-
110
- = 9.4.1 =
111
- * Release Date: July 9, 2015
112
- * Fixed: SQL injection security issue for users with access to the admin pages.
113
- * Fixed: Bug in code to save new "Treat corrupt browser info as a bot" setting.
114
- * Fixed: Bug in scheduled data pruge code that would not append the correct table prefix.
115
- * Updated: Admin manual.
116
-
117
- = 9.4 =
118
- * Release Date: July 3, 2015
119
- * Added: Date selector to top visitors page.
120
- * Added: Option to exclude WordPress's "Not Found" page from the statistics.
121
- * Added: Option to treat corrupt http header information as bots (missing IP addresses or user agents).
122
- * Added: New robots to list; 007ac9, 5bot, advbot, alphabot, anyevent, blexbot, bubing, cliqzbot, dl2bot, duckduckgo, EveryoneSocialBot, findxbot, glbot, linkapediabot, ltx71, mediabot, medialbot, monobot, OrangeBot, owler, pageanalyzer, porkbun, pr-cy, pwbot, r4bot, revip, riddler, rogerbot, sistrix, SputnikBot, u2bot, uni5download, unrulymedia, wsowner, wsr-agent, x100bot and xzybot
123
- * Fixed: Make sure the admin bar only appears for users that have read/manage permissions in WP Statistics.
124
- * Updated: Split the access and exclusions tabs in settings.
125
-
126
- = 9.3.1 =
127
- * Release Date: May 15, 2015
128
- * Fixed: Typo in options name that caused the visitors map to never be displayed.
129
-
130
- = 9.3 =
131
- * Release Date: May 15, 2015
132
- * Added: Shortcode UI (aka ShortCake) support.
133
- * Added: Donation menu and dismissble banner on the overview page.
134
- * Added: Applebot, Superfeedr, jetmon, sfFeedReader and feedzirra to the robots list.
135
- * Added: Summary postbox on hit statistics page.
136
- * Added: Summary postbox on exclusions page.
137
- * Added: Date range selector on top countries page.
138
- * Added: Purge data based on visitor's hit count on the optimization page.
139
- * Added: Option to purge data based on visitor's hit count on a daily basis.
140
- * Added: Option to record the page title for search referrals that do not contain a query value.
141
- * Updated: Moved all ajax and pseudo ajax calls to use the standard WordPress ajax and init routines instead of using wp-load.php.
142
- * Updated: Widgets and pages will only be displayed if the associated statistics is being collected, for example the search engine referrals will only be displayed if the visitor tracking option is enabled.
143
- * Fixed: Typo in variable name for one of the dashboard widgets.
144
- * Fixed: PHP error when the $browser object wasn't an object when we checked the crawler property.
145
- * Fixed: Incorrect parameter for get_option() on two option in the settings page.
146
- * Fixed: Widget's didn't translate correctly.
147
-
148
- = 9.2 =
149
- * Release Date: April 26, 2015
150
- * Added: Date range selector for charts now supports arbitrary date ranges with JavaScript date selector.
151
- * Added: If the site is using the blogroll for the homepage, use the blog title as the page name instead of leaving it blank.
152
- * Updated: How country codes are loaded for dashboard, widgets and pages.
153
- * Fixed: Incorrect URL in the admin manual.
154
- * Fixed: WP_DEBUG warning if formatting was not specified in the short code.
155
-
156
- = 9.1.3 =
157
- * Release Date: April 14, 2015
158
- * Added: Quick link to summary stats.
159
- * Added: Escaped text fields in the settings page with htmlentities() to protect against rouge administrators hijacking other admin sessions, thanks Kaustubh.
160
- * Fixed: Exclusions page had duplicate quotation marks in some JavaScript fields causing errors.
161
- * Fixed: Display of last_counter that is already set to the correct date and doesn't need to be adjusted for timezone.
162
-
163
- = 9.1.2 =
164
- * Release Date: March 20, 2015
165
- * Fixed: Removed spurious comma in SQL creation script for Visits table, thanks kitchin.
166
-
167
- = 9.1.1 =
168
- * Release Date: March 19, 2015
169
- * Fixed: Verify the $display settings return an array before using it as an array to avoid warning on overview page.
170
-
171
- = 9.1 =
172
- * Release Date: March 18, 2015
173
- * Added: Unique index requirement on visits table to avoid race condition creating duplicate entires.
174
- * Added: Option to the optimization page to remove duplicates and add new unique index to visits table on existing installs.
175
- * Updated: Translations, thanks to all of our translators!
176
- * Updated: Cleanup of some WP Debug warnings.
177
- * Fixed: JavaScript postboxes call was currupted on some pages causing a javascript error.
178
- * Fixed: Change html encode to jason_ecnode for data to be used in javascript to avoid single quotes as part of the translation breaking the javascript array, this change now fixes extended character display in the JavaScript charts.
179
- * Fixed: Verify $WP_Statistics is an object before using it, which was causing a fatal error on some installs.
180
- * Removed: Redudnent e modifier in preg_replace_callback to avoid php warning message.
181
-
182
- = 9.0 =
183
- * Release Date: March 12, 2015
184
- * Added: URL exclusions option.
185
- * Added: Swedish translation, thanks ronneborn.
186
- * Added: Kurdish (Sorani) translation, thanks sardar4it.
187
- * Added: Daily wp cron job to create an entry in the visits table for the next day to avoid a race condition.
188
- * Updated: The visits code now uses a SQL UPDATE instead of WP's update() to avoid a race condition.
189
- * Updated: Performance improvements in the last visitors page.
190
- * Updated: Performance improvements in the referrers page.
191
- * Updated: Added missing dash_icon call in online users page.
192
- * Updated: Make sure the $wp_object global variable is an object before using it, just in case, in the hits code.
193
- * Updated: Make sure the $wp_query global variable is an object before using it, just in case, in the hits code.
194
- * Updated: Removed variables from i18n functions for better translation support.
195
- * Updated: Removed requirement for date_default_timezone_set() which conflicted with some other plugins.
196
- * Updated: Make sure to html encode data to be used in javascript to avoid single quotes as part of the translation breaking the javascript array.
197
- * Updated: Change summary widget to be clearer about time frames.
198
- * Updated: Replace deprecated preg_replace (with /e) with preg_replace_callback. Thanks gbonvehi.
199
- * Updated: Use full path to ensure the require_once finds the purge file in the scheduled db maintenance script.
200
- * Updated: Persian translation.
201
- * Updated: Renamed pagination class to avoid name collisions with other plugins.
202
- * Updated: Date display in recent visitors and search words now uses the WordPress date format setting.
203
- * Updated: Upgrade email is now send at the end of the page load as wp_mail() hasn't been created during the upgrade script.
204
- * Fixed: Export code to handle large tables.
205
- * Fixed: Exclusion display for some 'reasons' always being 0.
206
- * Removed: Replaced use of global $table_prefix with $wpdb->prefix.
207
- * Removed: Use of deprecated $blog_id. Thanks gbonvehi.
208
-
209
- = 8.8.1 =
210
- * Release Date: March 9, 2015
211
- * Updated license to GPL3.
212
-
213
- = 8.8 =
214
- * Release Date: January 31, 2015
215
- * Added: Installation/upgrades/removals on WordPress multi-sites now upgrade all sites in the network if the installing user has the appropriate rights.
216
- * Added: RSS feed URL's can now be excluded.
217
- * Added: Option to set the country code for private IP addresses.
218
- * Fixed: Additional WP_DEBUG warning fixes.
219
- * Fixed: Incorrect parameter list in get_home_url() when checking for self referrals.
220
- * Fixed: Single quotes can now be used in the report content without being escaped.
221
- * Fixed: Referrers menu item was misspelled.
222
- * Updated: Italian, French, Polish, Arabic, Persian and Chinese translation.
223
- * Updated: Widget now formats numbers with international standards.
224
- * Updated: Short codes now support three number formatting options; i18n, english or none.
225
- * Updated: Removed old throttling code for hits which is no longer required.
226
- * Updated: IP address exclusions without a subnet mask now assume a single IP address instead of all IP addresses.
227
-
228
- = 8.7.2 =
229
- * Release Date: January 6, 2015
230
- * Added: shareaholic-bot to robots list.
231
- * Fixed: Robot threshold setting was not being saved.
232
- * Updated: Italian translation, thanks illatooscuro.
233
- * Updated: Arabic translation, thanks Hammad.
234
- * Updated: Honey pot page title now includes "Pot" in it.
235
-
236
- = 8.7.1 =
237
- * Release Date: December 28, 2014
238
- * Fixed: Variable scope for the exclusion match/reason updated to protected from private to allow the GeoIP code to set them. This could cause various issues including failed uploades depending on the error reporting level set for PHP.
239
-
240
- = 8.7 =
241
- * Release Date: December 27, 2014
242
- * Added: Charts with multiple lines now include the data set name in the tooltip.
243
- * Added: Honey pot option to detect crawlers.
244
- * Added: Robot threshold option.
245
- * Added: Hit count for visitors is now recorded and displayed.
246
- * Added: Top Visitors today widget and page.
247
- * Fixed: GeoIP exclusion logic didn't work as the location information was not set before it was applied, moved it to the appropriate location.
248
- * Fixed: Incorrect setting names for country include/excludes as well as hosts.
249
- * Fixed: Page URI length could exceed the database storage limit and cause duplicate entry warnings, URI is now truncated before being stored.
250
- * Updated: Polish and Farsi translations.
251
- * Updated: User agent parser to V0.3.2.
252
- * Updated: GeoIP library to v2.1.1.
253
-
254
- = 8.6.3 =
255
- * Release Date: December 11, 2014
256
- * Fixed: Really fix included countries code this time.
257
- * Fixed: Typo in excluded hosts code.
258
-
259
- = 8.6.2 =
260
- * Release Date: December 11, 2014
261
- * Fixed: New included countries code incorrectly identified all countries as excluded.
262
-
263
- = 8.6.1 =
264
- * Release Date: December 11, 2014
265
- * Added: Code to perform additional clean up of uncommon user agents.
266
- * Fixed: Spurious break statement in GeoIP exclusion code which caused a fatal error in certian cases.
267
-
268
- = 8.6 =
269
- * Release Date: December 11, 2014
270
- * Added: Option to remove URI parameters from page tracking.
271
- * Added: GeoIP exclusion options.
272
- * Added: Host name exclusion options.
273
- * Fixed: Map dashboard widget fails when Google is selected as map provider.
274
- * Fixed: Changing the statistical report schedule would not actually change the schedule unless you disabled and then enabled the statistical reports feature.
275
- * Updated: French language.
276
-
277
- = 8.5.1 =
278
- * Release Date: December 2, 2014
279
- * Fixed: Typo in last search page causing fatal error in PHP.
280
-
281
- = 8.5 =
282
- * Release Date: December 2, 2014
283
- * Added: try/catch condition around browscap call to avoid fatal errors stopping the script.
284
- * Added: Page trend widget to post/page editor.
285
- * Added: Aland Islands Flag icon.
286
- * Added: Option to record all online users regardless if they would otherwise be excluded.
287
- * Added: Option to disable the page editor widget.
288
- * Fixed: Various security fixes, thanks Ryan.
289
- * Fixed: Resolved warnings when natcasesort received a null list, thanks robertalks.
290
- * Fixed: Before updating the browscap.ini cache file, remove stale lock files.
291
- * Fixed: Avoid throwing a fatal error when the shutdown code is called if for some reason the global $WP_Statistics variable has been destroyed during a page load.
292
- * Updated: The online code now uses the same rules to exclude users as the hits code.
293
- * Updated: Minor code cleanups and data return checks.
294
- * Updated: German translations, thanks bios4.
295
- * Updated: Polish and Turkish translations.
296
- * Updated: Use built in WordPress function to translate user roles instead of custom strings in our PO file, thanks bios4.
297
-
298
- = 8.4 =
299
- * Release Date: November 26, 2014
300
- * Added: Dashboard widgets for all of the widgets on the overview page.
301
- * Added: Option to disable all dashboard widgets.
302
- * Added: Old dashboard widget upgraded with last 10 days of hits statistics.
303
- * Added: Online users page and time a user has been online.
304
- * Fixed: Fixed missing site_url on top 10 pages in the overview page.
305
- * Fixed: Incorrect url generated for Google map if dashboard was being forced in to https mode.
306
- * Fixed: Properly un-escape quotation marks in report body if magic quotes is enabled.
307
- * Fixed: URL referrer CSS style would 'push' other entires to the next line on small displays.
308
- * Fixed: Various PHP warnings on uninitalized variables, thanks bseddon
309
- * Updated: Polish translations.
310
- * Updated: Default map type now set to JQVMap.
311
-
312
- = 8.3.1 =
313
- * Release Date: November 19, 2014
314
- * Updated: Various SQL code clean ups.
315
- * Updated: Varioud data validation clean ups.
316
- * Updated: Various data output encoding updates, thanks Marc.
317
-
318
- = 8.3 =
319
- * Release Date: November 14, 2014
320
- * Added: Sanity checks for file size and results to browscap.ini updates, if the new cache file size is wrong or it mis-identifies a common real browser as a crawler the update will be rolled back.
321
- * Added: Option to e-mail a report on browscap.ini, database pruning, upgrades and GeoIP database updates.
322
- * Updated: Polish translations.
323
- * Updated: Added "Notificaitons" tab to the settings page and moved statistical report settings to it.
324
- * Fixed: The historical data table no longer uses reserved keywords as column names which caused issues on older versions of MySQL.
325
- * Fixed: Unable to set visits historical count.
326
- * Fixed: Purging did not record visits/visitors correctly if not already set through the optimization page.
327
- * Fixed: JavaScript bug when a non-administrative user viewed the settings page.
328
- * Removed: Reference to old settings file for the widget.
329
-
330
- = 8.2 =
331
- * Release Date: November 6, 2014
332
- * Added: Support for historical data.
333
- * Added: Removal option.
334
- * Updated: Optimized SQL statements to hopefully get rid of duplicate key error/warnings.
335
- * Updated: Persian, Polish, Italian translations.
336
- * Fixed: Duplicate date display on charts due to DST time change.
337
-
338
- = 8.1.1 =
339
- * Release Date: October 26, 2014
340
- * Fixed: Bug in browscap.ini update code that could mis-identify all hits as robots.
341
- * Fixed: Bug in the scheduled reports code that failed to process the report content correctly.
342
- * Fixed: Bug in schedule reports that failed to select the current schedule in the drop down.
343
- * Removed: Depricated variables from the report content description.
344
-
345
- = 8.1 =
346
- * Release Date: October 18, 2014
347
- * Added: Detected browser information to the optimization page.
348
- * Updated: Re-organized new browscap code to avoid PHP 5.2 or below throwing a parse error.
349
- * Fixed: If the client sent no user agent string a fatal error would be generated, added additional logic to handle this case.
350
- * Removed: Unused code in various log displays.
351
-
352
- = 8.0 =
353
- * Release Date: October 16, 2014
354
- * Added: browscap.ini support for robot detection.
355
- * Added: Statistics->Optimization->Database tab now how an option to re-run the install routine in case you have had to delete tables from the database.
356
- * Added: PHP version check, WP Statistics now requires PHP 5.3 and will no longer execute without it.
357
- * Added: Dashboard widget.
358
- * Updated: Top pages now decode the URL for better readability.
359
- * Updated: GeoIP library from version 0.5 to 2.0.
360
- * Updated: User Agent detection code.
361
- * Updated: Serbian, Polish translations.
362
- * Updated: All missing language strings have been machine translated when possible.
363
- * Updated: IP hashing code has moved out of beta.
364
- * Fixed: Incorrect country name being displayed for Georgia.
365
- * Fixed: Bug in detecting the new index in the Statistics->Optimization->Database tab.
366
- * Fixed: Duplicate closing tag in summary page.
367
- * Fixed: Purging the database did not display the results.
368
- * Removed: Support for old format substitution codes in the statistics reports, upgrade now converts them to short codes.
369
-
370
- = 7.4 =
371
- * Release Date: September 19, 2014
372
- * Added: Link URL for referred.
373
- * Updated: Widget code now adhears to WordPress standards.
374
- * Updated: Persian, Arabic and German (thanks Mike) translations.
375
- * Updated: Unique index on visitors table now takes in to account the agent/platform/version information.
376
- * Updated: Line charts now redraw when the legend is clicked to add/remove a line.
377
- * Fixed: Dates on charts with large number of data points now no longer overwrite each other.
378
- * Fixed: Admin bar menu item would use the incorrect admin URL in some circumstances.
379
- * Removed: Screenshots are no longer included in the distribution.
380
-
381
- = 7.3 =
382
- * Release Date: September 8, 2014
383
- * Added: Option to delete the admin manual.
384
- * Added: Option to force the robots list to be updated during an upgrade.
385
- * Added: Beta code for not storing IP addresses in the database.
386
- * Fixed: Bug with new JQVMap code not displaying flags correctly.
387
- * Updated: French (fr_FR) language, thanks apeedn.
388
- * Updated: Visitors online code now treats different browsers/platforms from the same IP address as different users (this helps with multiple users behind proxy servers).
389
- * Updated: Visitors code now treats different browsers/platforms from the same IP address as different users (this helps with multiple users behind proxy servers).
390
- * Updated: Persian (fa_IR) language.
391
- * Updated: Tested with WordPress 4.0.
392
-
393
- = 7.2 =
394
- * Release Date: August 22, 2014
395
- * Added: Total visitors by country to the push pins on the overview map.
396
- * Added: Statistical reports can now be sent to a custom list of e-mail addresses instead of just the administrator.
397
- * Added: JQVMap option for the overview map.
398
- * Fixed: Additional WP_DEBUG warnings cleaned up.
399
- * Fixed: Google map would sometimes only use part of the area to draw the map in the overview page.
400
- * Updated: Statistical report schedules are now listed by occurrence instead of randomly.
401
- * Updated: Vertical alignment of statistical report option label column now correct.
402
- * Updated: Various grammatical updates.
403
- * Updated: Overview map now limits the number of visitors to five per country.
404
- * Updated: Persian (fa_IR) language.
405
-
406
- = 7.1 =
407
- * Release Date: August 13, 2014
408
- * Added: clearch.org search provider, disabled by default.
409
- * Added: Database tab to optimization page to manually add unique index on the visitors table removed in 7.0.3.
410
- * Updated: Additional WP_DEBUG message fixes.
411
- * Updated: Overview widgets no longer overflows on smaller displays.
412
- * Updated: Charts now properly resize when the browser window does.
413
-
414
- = 7.0.4 =
415
- * Release Date: August 9, 2014
416
- * Fixed: Typo in table definition of visitor table's UAString field.
417
-
418
- = 7.0.3 =
419
- * Release Date: August 8, 2014
420
- * Added: Extra check that the co-efficient setting is valid.
421
- * Updated: Format of the dbDetla scripts to match the guidelines from WordPress, thanks kitchin.
422
- * Updated: Handled some WP_DEBUG warning messages, thanks kitchin.
423
- * Updated: Multiple additional WP_DEBUG warning fixes.
424
- * Updated: Arabic (ar) language.
425
- * Updated: Polish (pl_PL) language.
426
- * Fixed: Typo in variable name which causes the robots list to be overwritten with the defaults incorrectly.
427
- * Fixed: Access role exclusions and search engine exclusions options not displaying correctly in the settings page.
428
- * Removed: Database upgrade code to add the unique index on the visitors table due to issues with multiple users. Will add back in a future release as a user selectable option.
429
-
430
- = 7.0.2 =
431
- * Release Date: August 7, 2014
432
- * Fixed: Database prefix not being used when creating/updating tables correctly.
433
- * Fixed: New installs caused an error in the new upgrade code as the visitor table did not exist yet.
434
- * Fixed: Replaced use of deprecated $table_prefix global during install/update.
435
-
436
- = 7.0.1 =
437
- * Release Date: August 5, 2014
438
- * Fixed: Error during new installations due to $wpdb object not being available.
439
-
440
- = 7.0 =
441
- * Release Date: August 5, 2014
442
- * Added: New robots to the robots list: aiHitBot, AntivirusPro, BeetleBot, Blekkobot, cbot, clumboot, coccoc, crowsnest.tv, dbot, dotbot, downloadbot, EasouSpider, Exabot, facebook.com, FriendFeedBot, gimme60bot, GroupHigh, IstellaBot, Kraken, LinkpadBot, MojeekBot, NetcraftSurveyAgent, p4Bot, PaperLiBot, Pimonster, scrapy.org, SearchmetricsBot, SemanticBot, SemrushBot, SiteExplorer, Socialradarbot, SpiderLing, uMBot-LN, Vagabondo, vBSEO, WASALive-Bot, WebMasterAid, WeSEE, XoviBot, YoudaoBot,
443
- * Added: Overview page can now be customized for what is displayed on a per user basis.
444
- * Added: Overview tab to the settings page to control what is displayed. This page is available to any user that has read access to WP Statistics.
445
- * Added: Dutch (nl_NL) translation, thanks Friso van Wieringen.
446
- * Added: New index on visitor table for existing installs to avoid duplicate entries being created.
447
- * Added: jqPlot javascript library.
448
- * Added: Three new schedule options for statistical reports; weekly, bi-weekly and every 4 weeks.
449
- * Fixed: Some country codes not displaying in the "Top Countries" overview widget/page.
450
- * Fixed: Export filename contained a colon, which is not a valid character.
451
- * Fixed: In some cases purging data in the optimization page would succeed but the UI would "re-activate".
452
- * Updated: All charts now use jqPlot instead of HighCharts so we are now fully GPL compliant.
453
- * Updated: "Top Referring Sites" on the overview page now only displays if there are entries to be displayed.
454
- * Updated: "Latest Search Words" on the overview page now only displays if there are entries to be displayed.
455
- * Updated: "Top Pages Visited" on the overview page now only displays if there are entries to be displayed.
456
- * Updated: About on the overview page box.
457
- * Updated: Settings page from css tabs to jQuery tabs.
458
- * Updated: Settings system (which used individual WordPress settings for each option) to a new unified system (uses a single WordPress setting and stores it as an array)
459
- * Updated: Optimization page from css tabs to jQuery tabs.
460
- * Updated: Install/Upgrade code to share a single code base.
461
- * Updated: Persian (fa_IR) language.
462
- * Updated: Arabic (ar) language.
463
- * Updated: rtl.css file for new version.
464
- * Updated: Lots of code comments.
465
- * Updated: Statistical report schedule list in settings is now dynamically generated.
466
- * Updated: WP-Statistics screenshots.
467
- * Removed: "Alternate map location" setting as it has been made redundant by the new overview display settings.
468
- * Removed: "Chart type" setting as chart types are now hard coded to the appropriate type for the data.
469
- * Removed: HighCharts javascript library.
470
- * Removed: Unused function objectToArray().
471
-
472
- = 6.1 =
473
- * Release Date: June 29, 2014
474
- * Added: Display of the current memory_limit setting from php.ini in the optimization page.
475
- * Added: New index on visitor table for new installs to avoid duplicate entries being created. A future update will add this index to existing installs but will need additional testing before it is implemented.
476
- * Added: Seychelles flag.
477
- * Updated: Support international number formats in statistics display.
478
- * Updated: Description of WordPress.org plugin link in plugin list.
479
- * Updated: Widget and shortcode now use the countonly option in wp_statistics_vistor() for better performance.
480
- * Updated: Renamed plugin from "WordPress Statistics" to "WP Statistics".
481
- * Fixed: bug in new IP validation code and support for stripping off port numbers if they are passed through the headers. Thanks Stephanos Io.
482
- * Updated: Persian (fa_IR) language.
483
-
484
- = 6.0 =
485
- * Release Date: June 11, 2014
486
- * Added: Page tracking support. Includes new overview widget and detail page. Also supports page hit count in the pages/post list and in the page/post editor.
487
- * Added: Admin manual, online viewing as well as downloadable version.
488
- * Added: Links for “Settings”, “WordPress Plugin Page” and “Rate” pages to the plugin list for WP Statistics.
489
- * Updated: General settings tab re-organization.
490
- * Updated: Several typo's and other minor issues.
491
- * Updated: Highcharts JS v3.0.9 to JS v4.0.1.
492
- * Updated: Persian (fa_IR) language.
493
- * Updated: Polish (pl_PL) language.
494
- * Updated: Arabic (ar) language.
495
- * Updated: Turkish (tr_TR) language.
496
- * Removed: shortcode and functions reference from readme.txt, now in admin manual.
497
-
498
- = 5.4 =
499
- * Release Date: May 31, 2014
500
- * Fixed: GeoIP dependency code to ignore safe mode check in PHP 5.4 or newer.
501
- * Fixed: GeoIP dependency code to properly detect safe mode with PHP 5.3 or older.
502
- * Fixed: Browser information not recorded if GeoIP was not enabled.
503
- * Updated: get_IP code to better handle malformed IP addresses.
504
- * Updated: Persian (fa_IR) language.
505
- * Updated: Arabic (ar) language.
506
- * Updated: Chinese (zh_CN) language.
507
-
508
- = 5.3 =
509
- * Release Date: April 17, 2014
510
- * Added: New robot's to the robots list: BOT for JCE, Leikibot, LoadTimeBot, NerdyBot, niki-bot, PagesInventory, sees.co, SurveyBot, trendictionbot, Twitterbot, Wotbox, ZemlyaCrawl
511
- * Added: Check for PHP's Safe Mode as the GeoIP code does not function with it enabled.
512
- * Added: Option to disable administrative notices of inactive features.
513
- * Added: Option to export column names as first line of export files.
514
- * Added: Options to disable search engines from being collected/displayed.
515
- * Updated: French (fr_FR) language translation.
516
- * Fixed: Download of the GeoIP database could cause a fatal error message at the end of a page if it was triggered outside the admin area.
517
-
518
- = 5.2 =
519
- * Release Date: March 10, 2014
520
- * Added: Additional checks for BC Math and cURL which are required for the GeoIP code.
521
- * Updated: GeoIP database handling if it is missing or invalid.
522
- * Updated: GeoIP database is now stored in uploads/wp-statistics directory so it does not get overwritten during upgrades.
523
- * Fixed: Typo's in the shortcode codes (thanks g33kg0dd3ss).
524
- * Updated: Polish (pl_PL) language.
525
-
526
- = 5.1 =
527
- * Release Date: March 3, 2014
528
- * Fixes: Small bug in referral url.
529
- * Fixes: Problem export table.
530
- * Updated: Arabic (ar) language.
531
-
532
- = 5.0 =
533
- * Release Date: March 2, 2014
534
- * Added: Show last visitor in Google Map.
535
- * Added: Search visitor by IP in log pages.
536
- * Added: Total line to charts with multiple values, like the search engine referrals.
537
- * Added: Shortcodes. [By Greg Ross](http://profiles.wordpress.org/gregross)
538
- * Added: Dashicons to log pages.
539
- * Fixes: Small bugs.
540
- * Fixes: More debug warnings.
541
- * Fixes: User access function level code always returned manage_options no matter what it was actaully set to.
542
- * Updated: Hungarian (hu_HU) language.
543
- * Updated: Turkish (tr_TR) language.
544
- * Removed: Parameter from `wp_statistics_lastpostdate()` function and return date type became dynamic.
545
-
546
- = 4.8.1 =
547
- * Release Date: February 4, 2014
548
- * Fixes: Small bug in the `Current_Date`.
549
- * Fixes: Small bug in the `exclusions.php` file.
550
- * Updated: Polish (pl_PL) language.
551
-
552
- = 4.8 =
553
- * Release Date: February 4, 2014
554
- * Added: Converting Gregorian date to Persian When enabled [wp-parsidate](http://wordpress.org/plugins/wp-parsidate/) plugin.
555
- * Added: New feature, option to record the number and type of excluded hits to your site.
556
- * Added: New exclusion types for login and admin pages.
557
- * Fixes: GeoIP populate code now REALLY functions again.
558
- * Updated: Arabic (ar) language.
559
- * Updated: Polish (pl_PL) language.
560
-
561
- = 4.7 =
562
- * Release Date: February 2, 2014
563
- * Added: Responsive Stats page for smaller-screen devices.
564
- * Added: Dashicons icon for plugin page.
565
- * Added: Tabs option in setting page.
566
- * Added: Tabs category in optimization page.
567
- * Fixes: More debug warnings.
568
- * Fixes: GeoIP populate code now functions again.
569
- * Updated: Some optimization of the statistics code.
570
- * Updated: Search Words now reports results only for referrers with actual search queries.
571
- * Updated: Highcharts JS v3.0.7 to JS v3.0.9.
572
- * Updated: Brazil (pt_BR) language.
573
-
574
- = 4.6.1 =
575
- * Release Date: January 24, 2014
576
- * Fixes: a Small bug in to get rid of one of the reported warnings from debug mode.
577
-
578
- = 4.6 =
579
- * Release Date: January 20, 2014
580
- * Added: In the optimization page you can now empty all tables at once.
581
- * Added: In the optimization page you can now purge statistics over a given number of days old.
582
- * Added: Daily scheduled job to purge statistics over a given number of days old.
583
- * Fixes: Bug in the robots code that on new installs failed to populate the defaults in the database.
584
- * Fixes: All known warning messages when running in WordPress debug mode.
585
- * Fixes: Incorrect description of co-efficient value in the setting page.
586
- * Fixes: Top level links on the various stats pages now update highlight the current page in the admin menu instead of the overview page.
587
- * Fixes: Install code now only executes on a true new installation instead of on each activation.
588
- * Fixes: Bug in hits code when GeoIP was disabled, IP address would not be recorded.
589
-
590
- = 4.5 =
591
- * Release Date: January 18, 2014
592
- * Added: Support for more search engines: DuckDuckGo, Baidu and Yandex.
593
- * Added: Support for Google local sites like google.ca, google.fr, etc.
594
- * Added: Anchor links in the optimization and settings page to the main sections.
595
- * Added: Icon for Opera Next.
596
- * Updated: Added new bot match strings: 'archive.org_bot', 'meanpathbot', 'moreover', 'spbot'.
597
- * Updated: Replaced bot match string 'ezooms.bot' with 'ezooms'.
598
- * Updated: Overview summary statistics layout.
599
- * Fixes: Bug in widget code that didn't allow you to edit the settings after adding the widget to your site.
600
-
601
- = 4.4 =
602
- * Release Date: January 16, 2014
603
- * Added: option to set the required capability level to view statistics in the admin interface.
604
- * Added: option to set the required capability level to manage statistics in the admin interface.
605
- * Fixes: 'See More' links on the overview page now update highlight the current page in the admin menu instead of the overview page.
606
- * Added: Schedule downloads of the GeoIP database.
607
- * Added: Auto populate missing GeoIP information after a download of the GeoIP database.
608
- * Fixes: Unschedule of report event if reporting is disabled.
609
-
610
- = 4.3.1 =
611
- * Release Date: January 13, 2014
612
- * Fixes: Critical bug that caused only a single visitor to be recorded.
613
- * Added: Version information to the optimization page.
614
- [Thanks Greg Ross](http://profiles.wordpress.org/gregross)
615
-
616
- = 4.3 =
617
- * Release Date: January 12, 2014
618
- * Added: Definable robots list to exclude based upon the user agent string from the client.
619
- * Added: IP address and subnet exclusion support.
620
- * Added: Client IP and user agent information to the optimization page.
621
- * Added: Support to exclude users from data collection based on their WordPress role.
622
- * Fixes: A bug when the GeoIP code was disabled with optimization page.
623
-
624
- = 4.2 =
625
- * Release Date: December 31, 2013
626
- * Added: Statistical menus.
627
- * Fixes: Small bug in the geoip version.
628
- * Language: Serbian (sr_RS) was updated.
629
- * Language: German (de_DE) was updated.
630
- * Language: French (fr_FR) was updated.
631
-
632
- = 4.1 =
633
- * Release Date: December 23, 2013
634
- * Language: Arabic (ar) was updated
635
- * Fixes: small bug in moved the GeoIP database.
636
- * Updated: update to the spiders list.
637
-
638
- = 4.0 =
639
- * Release Date: December 21, 2013
640
- * Added: GeoIP location support for visitors country.
641
- * Added: Download option in settings for GeoIP database.
642
- * Added: Populate location entries with unknown or missing location information to the optimization page.
643
- * Added: Detect self referrals and disregard them like webcrawlers.
644
- * Added: "All Browsers" and "Top Countries" pages.
645
- * Added: "more" page to hit statistics chart, support for charts from 10 days to 1 year.
646
- * Added: "more" page to search engine statistics chart, support for charts from 10 days to 1 year.
647
- * Added: Option to store complete user agent string for debugging purposes.
648
- * Added: Option to delete specific browser or platform types from the database in the optimization page.
649
- * Updated: Browser detection now supports more browsers and includes platform and version information.
650
- * Updated: List of webcrawlers to catch more bots.
651
- * Updated: Statistics reporting options in settings no longer needs a page reload to hide/show the settings.
652
- * Updated: Summary Statistcs now uses the WordPress set format for the time and date.
653
- * Fixes: Webcrawler detection now works and is case insensitive.
654
- * Fixes: Install code now correctly sets defaults.
655
- * Fixes: Upgrade code now works correctly. If you are running V3.2, your old data will be preserved, older versions will delete the tables and recreate them.
656
- * Fixes: Ajax submissions on the optmiziation page (like the empty table function) should work in IE and other browsers that are sensitive to cross site attacks.
657
- * Fixes: Replaced call to the dashboard code (to support the postbox widgets on the log screen) with the proper call to the postbox code as WordPress 3.8 beta 1 did not work with the old code.
658
- * Updated: Highcharts JS 3.0.1 to JS 3.0.7 version.
659
-
660
- = 3.2 =
661
- * Release Date: August 7, 2013
662
- * Added: Optimization plugin page.
663
- * Added: Export data to excel, xml, csv and tsv files.
664
- * Added: Delete table data.
665
- * Added: Show memory usage in optimization page.
666
- * Language: Polish (pl_PL) was updated.
667
- * Language: updated.
668
-
669
- = 3.1.4 =
670
- * Release Date: July 18, 2013
671
- * Added: Chart Type in the settings plugin.
672
- * Added: Search Engine referrer chart in the view stats page.
673
- * Added: Search Engine stats in Summary Statistics.
674
- * Optimized: 'wp_statistics_searchengine()' and add second parameter in the function.
675
- * Language: Chinese (China) was added.
676
- * Language: Russian was updated.
677
- * Language: updated.
678
-
679
- = 3.1.3 =
680
- * Release Date: June 9, 2013
681
- * Optimized: View statistics.
682
- * Added: Chinese (Taiwan) language.
683
-
684
- = 3.1.2 =
685
- * Release Date: June 4, 2013
686
- * Added: Top referring sites with full details.
687
- * Resolved: Loads the plugin's translated strings problem.
688
- * Resolved: View the main site in top referring sites.
689
- * Resolved: Empty referrer.
690
- * Resolved: Empty search words.
691
- * Update: Highcharts js 2.3.5 to v3.0.1.
692
- * Language: Arabic was updated.
693
- * Language: Hungarian was updated.
694
- * Language: updated.
695
-
696
- = 3.1.1 =
697
- * Release Date: April 11, 2013
698
- * Bug Fix: Security problem. (Thanks Mohammad Teimori) for report bug.
699
- * Optimized: Statistics screen in resolution 1024x768.
700
- * Language: Persian was updated.
701
-
702
- = 3.1.0 =
703
- * Release Date: April 3, 2013
704
- * Bug Fix: Statistics Menu bar.
705
- * Bug Fix: Referral link of the last visitors.
706
- * Added: Latest all search words with full details.
707
- * Added: Recent all visitors with full details.
708
- * Optimized: View statistics.
709
- * Language: updated.
710
- * Language: Arabic was updated.
711
- * Remove: IP Information in setting page.
712
-
713
- = 3.0.2 =
714
- * Release Date: February 5, 2013
715
- * Added: Hungarian language.
716
- * Added: Insert value in useronline table by Primary_Values function.
717
- * Added: Opera browser in get_UserAgent function.
718
- * Added: prefix wps_ in options.
719
- * Added: Notices to enable or disable the plugin.
720
- * Changed: Statistics class to WP_Statistics because Resemblance name.
721
-
722
- = 3.0.1 =
723
- * Release Date: February 3, 2013
724
- * Bug Fix: Table plugin problem.
725
-
726
- = 3.0 =
727
- * Release Date: February 3, 2013
728
- * Bug Fix: problem in calculating Statistics.
729
- * Optimized: and speed up the process.
730
- * Optimized: Overall reconstruction and coding plug with a new structure.
731
- * Optimized: The use of object-oriented programming.
732
- * Added: statistics screen to complete.
733
- * Added: Chart Show.
734
- * Added: Graph of Browsers.
735
- * Added: Latest search words.
736
- * Added: Specification (Country and county) Visitors.
737
- * Added: Top referring sites.
738
- * Added: Send stats to Email/[SMS](http://wordpress.org/extend/plugins/wp-sms/)
739
-
740
- = 2.3.3 =
741
- * Release Date: December 18, 2012
742
- * Serbian language was solved.
743
- * Server variables were optimized by m.emami.
744
- * Turkish translation was complete.
745
-
746
- = 2.3.2 =
747
- * Release Date: October 24, 2012
748
- * Added Indonesia language.
749
- * Turkish language file corrected by MBOZ.
750
-
751
- = 2.3.1 =
752
- * Release Date: October 12, 2012
753
- * Added Polish language.
754
- * Added Support forum link in menu.
755
- * Fix problem error in delete plugin.
756
-
757
- = 2.3.0 =
758
- * Release Date: Not released
759
- * Added Serbian language.
760
-
761
- = 2.2.9 =
762
- * Release Date: September 20, 2012
763
- * Added Bengali language.
764
-
765
- = 2.2.8 =
766
- * Release Date: July 27, 2012
767
- * Added Russian language.
768
- * Fix problem in count views.
769
- * Added more filter for check spider.
770
- * Optimize plugin.
771
-
772
- = 2.2.7 =
773
- * Release Date: May 20, 2012
774
- * Fix problem in widget class.
775
- * Redundancy in Arabic translation.
776
- * Fix problem in [countposts] shortcode.
777
- * Optimized Style Reports.
778
-
779
- = 2.2.6 =
780
- * Release Date: April 19, 2012
781
- * Fix a small problem.
782
-
783
- = 2.2.5 =
784
- * Release Date: April 18, 2012
785
- * The security problem was solved. Please be sure to update!
786
- * Redundancy in French translation.
787
- * Add CSS Class for the containing widget. (Thanks Luai Mohammed).
788
- * Add daily or total search engines in setting page.
789
- * Using wordpress jQuery in setting page.
790
-
791
- = 2.2.4 =
792
- * Release Date: March 12, 2012
793
- * Added Turkish language.
794
- * Added Italian language.
795
- * Added German language.
796
- * Arabic language was solved.
797
- * Romanian language was solved.
798
- * The words in setting page were complete. (Thanks Will Abbott) default.po file is Updated.
799
- * The change of time from minutes to seconds to check users online.
800
- * Ignoring search engine crawler.
801
- * Added features premium version to free version.
802
- * Added user online live.
803
- * Added total visit live.
804
- * Added Increased to visit.
805
- * Added Reduced to visit.
806
- * Added Coefficient statistics for each user.
807
-
808
- = 2.2.3 =
809
- * Release Date: February 3, 2012
810
- * Optimized Counting.
811
- * Added Arabic language.
812
- * Draging problem was solved in Widgets
813
- * css problem was solved in sidebar
814
-
815
- = 2.2.2 =
816
- * Release Date: January 11, 2012
817
- * Solving show functions in setting page.
818
- * Solving month visit in widget.
819
- * Added Spanish language.
820
-
821
- = 2.2.1 =
822
- * Release Date: December 27, 2011
823
- * Solving drap uploader problem in media-new.php.
824
-
825
- = 2.2.0 =
826
- * Release Date: December 26, 2011
827
- * Added statistics to admin bar wordpress 3.3.
828
- * Added Uninstall for remove data and table from database.
829
- * Added all statistics item in widget and Their choice.
830
- * Optimize show function code in setting page.
831
- * Calling jQuery in wordpress admin for plugin.
832
- * Remove the word "disabled" in the statistics When the plugin was disabled.
833
- * Solving scroll problem in statistics page.
834
-
835
- = 2.1.6 =
836
- * Release Date: October 21, 2011
837
- * Added Russian language.
838
-
839
- = 2.1.5 =
840
- * Release Date: October 29, 2011
841
- * Added French language.
842
- * Rounds a float Averages.
843
-
844
- = 2.1.4 =
845
- * Release Date: October 21, 2011
846
- * Added Romanian language.
847
-
848
- = 2.1.3 =
849
- * Release Date: October 14, 2011
850
- * Active plugin in setting page was solved.
851
-
852
- = 2.1.2 =
853
- * Release Date: October 12, 2011
854
- * Added default language file.
855
- * Added Portuguese language.
856
-
857
- = 2.1.1 =
858
- * Release Date: September 27, 2011
859
- * Complete files
860
-
861
- = 2.1 =
862
- * Release Date: September 25, 2011
863
- * Edit string
864
-
865
- = 2.0 =
866
- * Release Date: September 20, 2011
867
- * Support from Database
868
- * Added Setting Page
869
- * Added decimals number
870
- * Added Online user check time
871
- * Added Database check time
872
- * Added User Online
873
- * Added Today Visit
874
- * Added Yesterday Visit
875
- * Added Week Visit
876
- * Added Month Visit
877
- * Added Years Visit
878
- * Added Search Engine reffered
879
- * Added Average Posts
880
- * Added Average Comments
881
- * Added Average Users
882
- * Added Google Pagerank
883
- * Added Alexa Pagerank
884
- * Added wordpress shortcode
885
-
886
- = 1.0 =
887
- * Release Date: March 20, 2011
888
- * Start plugin
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
readme.txt CHANGED
@@ -4,7 +4,7 @@ Donate link: https://wp-statistics.com/donate/
4
  Tags: analytics, wordpress analytics, stats, statistics, visit, visitors, hits, chart, browser, today, yesterday, week, month, year, total, post, page, sidebar, google, live visit, search word, agent, google analytics, webmasters, google webmasters, geoip, location
5
  Requires at least: 3.0
6
  Tested up to: 5.1
7
- Stable tag: 12.6.3
8
  License: GPLv3
9
  License URI: http://www.gnu.org/licenses/gpl-3.0.html
10
 
@@ -113,6 +113,9 @@ If IPv6 is not enabled, you may see an warning like:
113
  10. Theme widget
114
 
115
  == Upgrade Notice ==
 
 
 
116
  = 12.5.3 =
117
  Please consider that after updating, you will probably see some changes in Hits. The reason is that we have better-recognized crawlers and robots to get more accurate statistics for you.
118
 
@@ -122,6 +125,11 @@ http://yourwebsite.com/wp-json/wpstatistics/v1
122
  To register, go to the Permalink page and update the permalink with press Save Changes.
123
 
124
  == Changelog ==
 
 
 
 
 
125
  = 12.6.3 =
126
  * Fixed date picker issue in Top Visitors page.
127
  * Improved: Minor issues.
4
  Tags: analytics, wordpress analytics, stats, statistics, visit, visitors, hits, chart, browser, today, yesterday, week, month, year, total, post, page, sidebar, google, live visit, search word, agent, google analytics, webmasters, google webmasters, geoip, location
5
  Requires at least: 3.0
6
  Tested up to: 5.1
7
+ Stable tag: 12.6.4
8
  License: GPLv3
9
  License URI: http://www.gnu.org/licenses/gpl-3.0.html
10
 
113
  10. Theme widget
114
 
115
  == Upgrade Notice ==
116
+ = 12.5.4 =
117
+ If you have a problem in getting visitor's IP, just go to Statistics > Settings > Visitor IP.
118
+
119
  = 12.5.3 =
120
  Please consider that after updating, you will probably see some changes in Hits. The reason is that we have better-recognized crawlers and robots to get more accurate statistics for you.
121
 
125
  To register, go to the Permalink page and update the permalink with press Save Changes.
126
 
127
  == Changelog ==
128
+ = 12.6.4 =
129
+ * Added: The Visitor IP configuration in the setting page for choosing method that gets visitor's IP addresses.
130
+ * Improved: Minor issues in datepicker.
131
+ * Improved: Minor issues in search referrer.
132
+
133
  = 12.6.3 =
134
  * Fixed date picker issue in Top Visitors page.
135
  * Improved: Minor issues.
wp-statistics.php CHANGED
@@ -3,7 +3,7 @@
3
  * Plugin Name: WP Statistics
4
  * Plugin URI: https://wp-statistics.com/
5
  * Description: Complete WordPress Analytics and Statistics for your site!
6
- * Version: 12.6.3
7
  * Author: VeronaLabs
8
  * Author URI: http://veronalabs.com/
9
  * Text Domain: wp-statistics
3
  * Plugin Name: WP Statistics
4
  * Plugin URI: https://wp-statistics.com/
5
  * Description: Complete WordPress Analytics and Statistics for your site!
6
+ * Version: 12.6.4
7
  * Author: VeronaLabs
8
  * Author URI: http://veronalabs.com/
9
  * Text Domain: wp-statistics