From 731cdcce7d1d66f9437d5aa255082bf82502e252 Mon Sep 17 00:00:00 2001 From: Dan Cryer Date: Tue, 3 Feb 2015 11:10:22 +0000 Subject: [PATCH 01/11] Fixing missing docblock error. --- PHPCI/Model/Build.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PHPCI/Model/Build.php b/PHPCI/Model/Build.php index b5928cfe..5561d51e 100644 --- a/PHPCI/Model/Build.php +++ b/PHPCI/Model/Build.php @@ -194,6 +194,10 @@ class Build extends BuildBase return $rtn; } + /** + * Returns the commit message for this build. + * @return string + */ public function getCommitMessage() { $rtn = htmlspecialchars($this->data['commit_message']); From 3ade895e30f03db75d9a20f0f6865b097a85ba85 Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Wed, 14 Jan 2015 20:56:37 +0100 Subject: [PATCH 02/11] Fix parameters used to check if ssh-keygen exists to prevent an indefinite hang. Closes #764 --- PHPCI/Helper/SshKey.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PHPCI/Helper/SshKey.php b/PHPCI/Helper/SshKey.php index ca12a815..47bd85bd 100644 --- a/PHPCI/Helper/SshKey.php +++ b/PHPCI/Helper/SshKey.php @@ -63,7 +63,7 @@ class SshKey */ public function canGenerateKeys() { - $keygen = @shell_exec('ssh-keygen -h'); + $keygen = @shell_exec('ssh-keygen --help'); $canGenerateKeys = !empty($keygen); return $canGenerateKeys; From 9351e889e786703b0ef92be76aa31c4ed7ee1cf6 Mon Sep 17 00:00:00 2001 From: Dan Cryer Date: Tue, 3 Feb 2015 11:20:46 +0000 Subject: [PATCH 03/11] Fixing failing test --- Tests/PHPCI/Service/BuildServiceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/PHPCI/Service/BuildServiceTest.php b/Tests/PHPCI/Service/BuildServiceTest.php index 9b1097a3..6dff6385 100644 --- a/Tests/PHPCI/Service/BuildServiceTest.php +++ b/Tests/PHPCI/Service/BuildServiceTest.php @@ -56,7 +56,7 @@ class BuildServiceTest extends \PHPUnit_Framework_TestCase $this->assertNull($returnValue->getStarted()); $this->assertNull($returnValue->getFinished()); $this->assertNull($returnValue->getLog()); - $this->assertNull($returnValue->getCommitMessage()); + $this->assertEmpty($returnValue->getCommitMessage()); $this->assertNull($returnValue->getCommitterEmail()); $this->assertNull($returnValue->getExtra()); $this->assertEquals('master', $returnValue->getBranch()); From 96b1df55b0e2e34aa4c48eb781e58a795e923522 Mon Sep 17 00:00:00 2001 From: Sergey Linnik Date: Thu, 22 Jan 2015 12:28:16 +0300 Subject: [PATCH 04/11] Add query interpolation in PostgreSQL and SQLite plugin. Closes #757 --- PHPCI/Plugin/Pgsql.php | 2 +- PHPCI/Plugin/Sqlite.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PHPCI/Plugin/Pgsql.php b/PHPCI/Plugin/Pgsql.php index 9a5f630c..9d0f924d 100644 --- a/PHPCI/Plugin/Pgsql.php +++ b/PHPCI/Plugin/Pgsql.php @@ -83,7 +83,7 @@ class Pgsql implements \PHPCI\Plugin $pdo = new PDO('pgsql:host=' . $this->host, $this->user, $this->pass, $opts); foreach ($this->queries as $query) { - $pdo->query($query); + $pdo->query($this->phpci->interpolate($query)); } } catch (\Exception $ex) { $this->phpci->logFailure($ex->getMessage()); diff --git a/PHPCI/Plugin/Sqlite.php b/PHPCI/Plugin/Sqlite.php index 1ffdcc88..f80ece3d 100644 --- a/PHPCI/Plugin/Sqlite.php +++ b/PHPCI/Plugin/Sqlite.php @@ -70,7 +70,7 @@ class Sqlite implements \PHPCI\Plugin $pdo = new PDO('sqlite:' . $this->path, $opts); foreach ($this->queries as $query) { - $pdo->query($query); + $pdo->query($this->phpci->interpolate($query)); } } catch (\Exception $ex) { $this->phpci->logFailure($ex->getMessage()); From c441e72d0a1b7f80bb32cbae96a86ad466592c5b Mon Sep 17 00:00:00 2001 From: Adam Cooper Date: Wed, 21 Jan 2015 10:56:00 +0000 Subject: [PATCH 05/11] Move CSS into separate file, fix width of plugins that use tables and adds table-responsive class for smaller screens. Closes #755 --- PHPCI/View/layout.phtml | 47 +--------------- public/assets/css/AdminLTE-custom.css | 53 +++++++++++++++++++ public/assets/js/build-plugins/phpcpd.js | 4 +- public/assets/js/build-plugins/phpcs.js | 4 +- public/assets/js/build-plugins/phpdoccheck.js | 4 +- public/assets/js/build-plugins/phpmd.js | 4 +- public/assets/js/build-plugins/phptallint.js | 4 +- public/assets/js/build-plugins/phpunit.js | 4 +- 8 files changed, 66 insertions(+), 58 deletions(-) create mode 100644 public/assets/css/AdminLTE-custom.css diff --git a/PHPCI/View/layout.phtml b/PHPCI/View/layout.phtml index 1aca09c1..3375d9df 100644 --- a/PHPCI/View/layout.phtml +++ b/PHPCI/View/layout.phtml @@ -16,6 +16,7 @@ + + diff --git a/Tests/PHPCI/Helper/LangTest.php b/Tests/PHPCI/Helper/LangTest.php new file mode 100644 index 00000000..492cca52 --- /dev/null +++ b/Tests/PHPCI/Helper/LangTest.php @@ -0,0 +1,28 @@ +prophesize('DateTime'); + $dateTime->format(DateTime::ISO8601)->willReturn("ISODATE"); + $dateTime->format(DateTime::RFC2822)->willReturn("RFCDATE"); + + $this->assertEquals('', Lang::formatDateTime($dateTime->reveal(), 'FORMAT')); + } + + public function testLang_UseDefaultFormat() + { + $dateTime = $this->prophesize('DateTime'); + $dateTime->format(DateTime::ISO8601)->willReturn("ISODATE"); + $dateTime->format(DateTime::RFC2822)->willReturn("RFCDATE"); + + $this->assertEquals('', Lang::formatDateTime($dateTime->reveal())); + } +} diff --git a/public/assets/css/daterangepicker/daterangepicker-bs3.css b/public/assets/css/daterangepicker/daterangepicker-bs3.css index eed1e9f4..9ddabb1f 100755 --- a/public/assets/css/daterangepicker/daterangepicker-bs3.css +++ b/public/assets/css/daterangepicker/daterangepicker-bs3.css @@ -18,11 +18,16 @@ margin: 4px; } -.daterangepicker.opensright .ranges, .daterangepicker.opensright .calendar { +.daterangepicker.opensright .ranges, .daterangepicker.opensright .calendar, +.daterangepicker.openscenter .ranges, .daterangepicker.openscenter .calendar { float: right; margin: 4px; } +.daterangepicker.single .ranges, .daterangepicker.single .calendar { + float: none; +} + .daterangepicker .ranges { width: 160px; text-align: left; @@ -41,6 +46,14 @@ max-width: 270px; } +.daterangepicker.show-calendar .calendar { + display: block; +} + +.daterangepicker .calendar.single .calendar-date { + border: none; +} + .daterangepicker .calendar th, .daterangepicker .calendar td { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; white-space: nowrap; @@ -48,7 +61,8 @@ min-width: 32px; } -.daterangepicker .ranges label { +.daterangepicker .daterangepicker_start_input label, +.daterangepicker .daterangepicker_end_input label { color: #333; display: block; font-size: 11px; @@ -66,7 +80,6 @@ } .daterangepicker .ranges .input-mini { - background-color: #eee; border: 1px solid #ccc; border-radius: 4px; color: #555; @@ -153,6 +166,37 @@ content: ''; } +.daterangepicker.openscenter:before { + position: absolute; + top: -7px; + left: 0; + right: 0; + width: 0; + margin-left: auto; + margin-right: auto; + display: inline-block; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-left: 7px solid transparent; + border-bottom-color: rgba(0, 0, 0, 0.2); + content: ''; +} + +.daterangepicker.openscenter:after { + position: absolute; + top: -6px; + left: 0; + right: 0; + width: 0; + margin-left: auto; + margin-right: auto; + display: inline-block; + border-right: 6px solid transparent; + border-bottom: 6px solid #fff; + border-left: 6px solid transparent; + content: ''; +} + .daterangepicker.opensright:before { position: absolute; top: -7px; @@ -196,7 +240,7 @@ color: #999; } -.daterangepicker td.disabled { +.daterangepicker td.disabled, .daterangepicker option.disabled { color: #999; } @@ -211,6 +255,24 @@ border-radius: 0; } +.daterangepicker td.start-date { + -webkit-border-radius: 4px 0 0 4px; + -moz-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; +} + +.daterangepicker td.end-date { + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; +} + +.daterangepicker td.start-date.end-date { + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + .daterangepicker td.active, .daterangepicker td.active:hover { background-color: #357ebd; border-color: #3071a9; @@ -239,7 +301,20 @@ width: 40%; } -.daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.ampmselect { +.daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.secondselect, .daterangepicker select.ampmselect { width: 50px; margin-bottom: 0; } + +.daterangepicker_start_input { + float: left; +} + +.daterangepicker_end_input { + float: left; + padding-left: 11px +} + +.daterangepicker th.month { + width: auto; +} diff --git a/public/assets/js/phpci.js b/public/assets/js/phpci.js index f98ea959..7e0b0f8b 100644 --- a/public/assets/js/phpci.js +++ b/public/assets/js/phpci.js @@ -3,7 +3,16 @@ var PHPCI = { intervals: {}, init: function () { + // Setup the date locale + moment.locale(PHPCI_LANGUAGE); + $(document).ready(function () { + // Format datetimes + $('time[datetime]').each(function() { + var $this = $(this); + $this.text(moment(this.dateTime).format($this.data('format') || 'lll')); + }); + // Update latest builds every 5 seconds: PHPCI.getBuilds(); PHPCI.intervals.getBuilds = setInterval(PHPCI.getBuilds, 5000); diff --git a/public/assets/js/plugins/daterangepicker/daterangepicker.js b/public/assets/js/plugins/daterangepicker/daterangepicker.js index 11a00fa3..891a1bf4 100755 --- a/public/assets/js/plugins/daterangepicker/daterangepicker.js +++ b/public/assets/js/plugins/daterangepicker/daterangepicker.js @@ -1,60 +1,39 @@ -// moment.js -// version : 2.1.0 -// author : Tim Wood -// license : MIT -// momentjs.com -!function(t){function e(t,e){return function(n){return u(t.call(this,n),e)}}function n(t,e){return function(n){return this.lang().ordinal(t.call(this,n),e)}}function s(){}function i(t){a(this,t)}function r(t){var e=t.years||t.year||t.y||0,n=t.months||t.month||t.M||0,s=t.weeks||t.week||t.w||0,i=t.days||t.day||t.d||0,r=t.hours||t.hour||t.h||0,a=t.minutes||t.minute||t.m||0,o=t.seconds||t.second||t.s||0,u=t.milliseconds||t.millisecond||t.ms||0;this._input=t,this._milliseconds=u+1e3*o+6e4*a+36e5*r,this._days=i+7*s,this._months=n+12*e,this._data={},this._bubble()}function a(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}function o(t){return 0>t?Math.ceil(t):Math.floor(t)}function u(t,e){for(var n=t+"";n.lengthn;n++)~~t[n]!==~~e[n]&&r++;return r+i}function f(t){return t?ie[t]||t.toLowerCase().replace(/(.)s$/,"$1"):t}function l(t,e){return e.abbr=t,x[t]||(x[t]=new s),x[t].set(e),x[t]}function _(t){if(!t)return H.fn._lang;if(!x[t]&&A)try{require("./lang/"+t)}catch(e){return H.fn._lang}return x[t]}function m(t){return t.match(/\[.*\]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function y(t){var e,n,s=t.match(E);for(e=0,n=s.length;n>e;e++)s[e]=ue[s[e]]?ue[s[e]]:m(s[e]);return function(i){var r="";for(e=0;n>e;e++)r+=s[e]instanceof Function?s[e].call(i,t):s[e];return r}}function M(t,e){function n(e){return t.lang().longDateFormat(e)||e}for(var s=5;s--&&N.test(e);)e=e.replace(N,n);return re[e]||(re[e]=y(e)),re[e](t)}function g(t,e){switch(t){case"DDDD":return V;case"YYYY":return X;case"YYYYY":return $;case"S":case"SS":case"SSS":case"DDD":return I;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return R;case"a":case"A":return _(e._l)._meridiemParse;case"X":return B;case"Z":case"ZZ":return j;case"T":return q;case"MM":case"DD":case"YY":case"HH":case"hh":case"mm":case"ss":case"M":case"D":case"d":case"H":case"h":case"m":case"s":return J;default:return new RegExp(t.replace("\\",""))}}function p(t){var e=(j.exec(t)||[])[0],n=(e+"").match(ee)||["-",0,0],s=+(60*n[1])+~~n[2];return"+"===n[0]?-s:s}function D(t,e,n){var s,i=n._a;switch(t){case"M":case"MM":i[1]=null==e?0:~~e-1;break;case"MMM":case"MMMM":s=_(n._l).monthsParse(e),null!=s?i[1]=s:n._isValid=!1;break;case"D":case"DD":case"DDD":case"DDDD":null!=e&&(i[2]=~~e);break;case"YY":i[0]=~~e+(~~e>68?1900:2e3);break;case"YYYY":case"YYYYY":i[0]=~~e;break;case"a":case"A":n._isPm=_(n._l).isPM(e);break;case"H":case"HH":case"h":case"hh":i[3]=~~e;break;case"m":case"mm":i[4]=~~e;break;case"s":case"ss":i[5]=~~e;break;case"S":case"SS":case"SSS":i[6]=~~(1e3*("0."+e));break;case"X":n._d=new Date(1e3*parseFloat(e));break;case"Z":case"ZZ":n._useUTC=!0,n._tzm=p(e)}null==e&&(n._isValid=!1)}function Y(t){var e,n,s=[];if(!t._d){for(e=0;7>e;e++)t._a[e]=s[e]=null==t._a[e]?2===e?1:0:t._a[e];s[3]+=~~((t._tzm||0)/60),s[4]+=~~((t._tzm||0)%60),n=new Date(0),t._useUTC?(n.setUTCFullYear(s[0],s[1],s[2]),n.setUTCHours(s[3],s[4],s[5],s[6])):(n.setFullYear(s[0],s[1],s[2]),n.setHours(s[3],s[4],s[5],s[6])),t._d=n}}function w(t){var e,n,s=t._f.match(E),i=t._i;for(t._a=[],e=0;eo&&(u=o,s=n);a(t,s)}function v(t){var e,n=t._i,s=K.exec(n);if(s){for(t._f="YYYY-MM-DD"+(s[2]||" "),e=0;4>e;e++)if(te[e][1].exec(n)){t._f+=te[e][0];break}j.exec(n)&&(t._f+=" Z"),w(t)}else t._d=new Date(n)}function T(e){var n=e._i,s=G.exec(n);n===t?e._d=new Date:s?e._d=new Date(+s[1]):"string"==typeof n?v(e):d(n)?(e._a=n.slice(0),Y(e)):e._d=n instanceof Date?new Date(+n):new Date(n)}function b(t,e,n,s,i){return i.relativeTime(e||1,!!n,t,s)}function S(t,e,n){var s=W(Math.abs(t)/1e3),i=W(s/60),r=W(i/60),a=W(r/24),o=W(a/365),u=45>s&&["s",s]||1===i&&["m"]||45>i&&["mm",i]||1===r&&["h"]||22>r&&["hh",r]||1===a&&["d"]||25>=a&&["dd",a]||45>=a&&["M"]||345>a&&["MM",W(a/30)]||1===o&&["y"]||["yy",o];return u[2]=e,u[3]=t>0,u[4]=n,b.apply({},u)}function F(t,e,n){var s,i=n-e,r=n-t.day();return r>i&&(r-=7),i-7>r&&(r+=7),s=H(t).add("d",r),{week:Math.ceil(s.dayOfYear()/7),year:s.year()}}function O(t){var e=t._i,n=t._f;return null===e||""===e?null:("string"==typeof e&&(t._i=e=_().preparse(e)),H.isMoment(e)?(t=a({},e),t._d=new Date(+e._d)):n?d(n)?k(t):w(t):T(t),new i(t))}function z(t,e){H.fn[t]=H.fn[t+"s"]=function(t){var n=this._isUTC?"UTC":"";return null!=t?(this._d["set"+n+e](t),H.updateOffset(this),this):this._d["get"+n+e]()}}function C(t){H.duration.fn[t]=function(){return this._data[t]}}function L(t,e){H.duration.fn["as"+t]=function(){return+this/e}}for(var H,P,U="2.1.0",W=Math.round,x={},A="undefined"!=typeof module&&module.exports,G=/^\/?Date\((\-?\d+)/i,Z=/(\-)?(\d*)?\.?(\d+)\:(\d+)\:(\d+)\.?(\d{3})?/,E=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|SS?S?|X|zz?|ZZ?|.)/g,N=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,J=/\d\d?/,I=/\d{1,3}/,V=/\d{3}/,X=/\d{1,4}/,$=/[+\-]?\d{1,6}/,R=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,j=/Z|[\+\-]\d\d:?\d\d/i,q=/T/i,B=/[\+\-]?\d+(\.\d{1,3})?/,K=/^\s*\d{4}-\d\d-\d\d((T| )(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,Q="YYYY-MM-DDTHH:mm:ssZ",te=[["HH:mm:ss.S",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],ee=/([\+\-]|\d\d)/gi,ne="Date|Hours|Minutes|Seconds|Milliseconds".split("|"),se={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},ie={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",w:"week",M:"month",y:"year"},re={},ae="DDD w W M D d".split(" "),oe="M D H h m s w W".split(" "),ue={M:function(){return this.month()+1},MMM:function(t){return this.lang().monthsShort(this,t)},MMMM:function(t){return this.lang().months(this,t)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(t){return this.lang().weekdaysMin(this,t)},ddd:function(t){return this.lang().weekdaysShort(this,t)},dddd:function(t){return this.lang().weekdays(this,t)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return u(this.year()%100,2)},YYYY:function(){return u(this.year(),4)},YYYYY:function(){return u(this.year(),5)},gg:function(){return u(this.weekYear()%100,2)},gggg:function(){return this.weekYear()},ggggg:function(){return u(this.weekYear(),5)},GG:function(){return u(this.isoWeekYear()%100,2)},GGGG:function(){return this.isoWeekYear()},GGGGG:function(){return u(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return~~(this.milliseconds()/100)},SS:function(){return u(~~(this.milliseconds()/10),2)},SSS:function(){return u(this.milliseconds(),3)},Z:function(){var t=-this.zone(),e="+";return 0>t&&(t=-t,e="-"),e+u(~~(t/60),2)+":"+u(~~t%60,2)},ZZ:function(){var t=-this.zone(),e="+";return 0>t&&(t=-t,e="-"),e+u(~~(10*t/6),4)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},X:function(){return this.unix()}};ae.length;)P=ae.pop(),ue[P+"o"]=n(ue[P],P);for(;oe.length;)P=oe.pop(),ue[P+P]=e(ue[P],2);for(ue.DDDD=e(ue.DDD,3),s.prototype={set:function(t){var e,n;for(n in t)e=t[n],"function"==typeof e?this[n]=e:this["_"+n]=e},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(t){return this._months[t.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(t){return this._monthsShort[t.month()]},monthsParse:function(t){var e,n,s;for(this._monthsParse||(this._monthsParse=[]),e=0;12>e;e++)if(this._monthsParse[e]||(n=H([2e3,e]),s="^"+this.months(n,"")+"|^"+this.monthsShort(n,""),this._monthsParse[e]=new RegExp(s.replace(".",""),"i")),this._monthsParse[e].test(t))return e},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(t){return this._weekdays[t.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(t){return this._weekdaysShort[t.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(t){return this._weekdaysMin[t.day()]},weekdaysParse:function(t){var e,n,s;for(this._weekdaysParse||(this._weekdaysParse=[]),e=0;7>e;e++)if(this._weekdaysParse[e]||(n=H([2e3,1]).day(e),s="^"+this.weekdays(n,"")+"|^"+this.weekdaysShort(n,"")+"|^"+this.weekdaysMin(n,""),this._weekdaysParse[e]=new RegExp(s.replace(".",""),"i")),this._weekdaysParse[e].test(t))return e},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(t){var e=this._longDateFormat[t];return!e&&this._longDateFormat[t.toUpperCase()]&&(e=this._longDateFormat[t.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t]=e),e},isPM:function(t){return"p"===(t+"").toLowerCase()[0]},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(t,e){var n=this._calendar[t];return"function"==typeof n?n.apply(e):n},_relativeTime:{future:"in %s",past:"%s ago",s:"a few 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"},relativeTime:function(t,e,n,s){var i=this._relativeTime[n];return"function"==typeof i?i(t,e,n,s):i.replace(/%d/i,t)},pastFuture:function(t,e){var n=this._relativeTime[t>0?"future":"past"];return"function"==typeof n?n(e):n.replace(/%s/i,e)},ordinal:function(t){return this._ordinal.replace("%d",t)},_ordinal:"%d",preparse:function(t){return t},postformat:function(t){return t},week:function(t){return F(t,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6}},H=function(t,e,n){return O({_i:t,_f:e,_l:n,_isUTC:!1})},H.utc=function(t,e,n){return O({_useUTC:!0,_isUTC:!0,_l:n,_i:t,_f:e})},H.unix=function(t){return H(1e3*t)},H.duration=function(t,e){var n,s,i=H.isDuration(t),a="number"==typeof t,o=i?t._input:a?{}:t,u=Z.exec(t);return a?e?o[e]=t:o.milliseconds=t:u&&(n="-"===u[1]?-1:1,o={y:0,d:~~u[2]*n,h:~~u[3]*n,m:~~u[4]*n,s:~~u[5]*n,ms:~~u[6]*n}),s=new r(o),i&&t.hasOwnProperty("_lang")&&(s._lang=t._lang),s},H.version=U,H.defaultFormat=Q,H.updateOffset=function(){},H.lang=function(t,e){return t?(e?l(t,e):x[t]||_(t),H.duration.fn._lang=H.fn._lang=_(t),void 0):H.fn._lang._abbr},H.langData=function(t){return t&&t._lang&&t._lang._abbr&&(t=t._lang._abbr),_(t)},H.isMoment=function(t){return t instanceof i},H.isDuration=function(t){return t instanceof r},H.fn=i.prototype={clone:function(){return H(this)},valueOf:function(){return+this._d+6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){return M(H(this).utc(),"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]")},toArray:function(){var t=this;return[t.year(),t.month(),t.date(),t.hours(),t.minutes(),t.seconds(),t.milliseconds()]},isValid:function(){return null==this._isValid&&(this._isValid=this._a?!c(this._a,(this._isUTC?H.utc(this._a):H(this._a)).toArray()):!isNaN(this._d.getTime())),!!this._isValid},utc:function(){return this.zone(0)},local:function(){return this.zone(0),this._isUTC=!1,this},format:function(t){var e=M(this,t||H.defaultFormat);return this.lang().postformat(e)},add:function(t,e){var n;return n="string"==typeof t?H.duration(+e,t):H.duration(t,e),h(this,n,1),this},subtract:function(t,e){var n;return n="string"==typeof t?H.duration(+e,t):H.duration(t,e),h(this,n,-1),this},diff:function(t,e,n){var s,i,r=this._isUTC?H(t).zone(this._offset||0):H(t).local(),a=6e4*(this.zone()-r.zone());return e=f(e),"year"===e||"month"===e?(s=432e5*(this.daysInMonth()+r.daysInMonth()),i=12*(this.year()-r.year())+(this.month()-r.month()),i+=(this-H(this).startOf("month")-(r-H(r).startOf("month")))/s,i-=6e4*(this.zone()-H(this).startOf("month").zone()-(r.zone()-H(r).startOf("month").zone()))/s,"year"===e&&(i/=12)):(s=this-r,i="second"===e?s/1e3:"minute"===e?s/6e4:"hour"===e?s/36e5:"day"===e?(s-a)/864e5:"week"===e?(s-a)/6048e5:s),n?i:o(i)},from:function(t,e){return H.duration(this.diff(t)).lang(this.lang()._abbr).humanize(!e)},fromNow:function(t){return this.from(H(),t)},calendar:function(){var t=this.diff(H().startOf("day"),"days",!0),e=-6>t?"sameElse":-1>t?"lastWeek":0>t?"lastDay":1>t?"sameDay":2>t?"nextDay":7>t?"nextWeek":"sameElse";return this.format(this.lang().calendar(e,this))},isLeapYear:function(){var t=this.year();return 0===t%4&&0!==t%100||0===t%400},isDST:function(){return this.zone()+H(t).startOf(e)},isBefore:function(t,e){return e="undefined"!=typeof e?e:"millisecond",+this.clone().startOf(e)<+H(t).startOf(e)},isSame:function(t,e){return e="undefined"!=typeof e?e:"millisecond",+this.clone().startOf(e)===+H(t).startOf(e)},min:function(t){return t=H.apply(null,arguments),this>t?this:t},max:function(t){return t=H.apply(null,arguments),t>this?this:t},zone:function(t){var e=this._offset||0;return null==t?this._isUTC?e:this._d.getTimezoneOffset():("string"==typeof t&&(t=p(t)),Math.abs(t)<16&&(t=60*t),this._offset=t,this._isUTC=!0,e!==t&&h(this,H.duration(e-t,"m"),1,!0),this)},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},daysInMonth:function(){return H.utc([this.year(),this.month()+1,0]).date()},dayOfYear:function(t){var e=W((H(this).startOf("day")-H(this).startOf("year"))/864e5)+1;return null==t?e:this.add("d",t-e)},weekYear:function(t){var e=F(this,this.lang()._week.dow,this.lang()._week.doy).year;return null==t?e:this.add("y",t-e)},isoWeekYear:function(t){var e=F(this,1,4).year;return null==t?e:this.add("y",t-e)},week:function(t){var e=this.lang().week(this);return null==t?e:this.add("d",7*(t-e))},isoWeek:function(t){var e=F(this,1,4).week;return null==t?e:this.add("d",7*(t-e))},weekday:function(t){var e=(this._d.getDay()+7-this.lang()._week.dow)%7;return null==t?e:this.add("d",t-e)},isoWeekday:function(t){return null==t?this.day()||7:this.day(this.day()%7?t:t-7)},lang:function(e){return e===t?this._lang:(this._lang=_(e),this)}},P=0;P' + - '
' + + '
' + + '
' + '
' + '
' + - '
' + - '' + - '' + + '
' + + '' + + '' + '
' + - '
' + - '' + - '' + + '
' + + '' + + '' + '
' + - ' ' + - '' + + ' ' + + '' + '
' + '
' + '
'; - this.parentEl = (hasOptions && options.parentEl && $(options.parentEl)) || $(this.parentEl); - //the date range picker + //custom options + if (typeof options !== 'object' || options === null) + options = {}; + + this.parentEl = (typeof options === 'object' && options.parentEl && $(options.parentEl).length) ? $(options.parentEl) : $(this.parentEl); this.container = $(DRPTemplate).appendTo(this.parentEl); - if (hasOptions) { + this.setOptions(options, cb); - if (typeof options.format == 'string') + //apply CSS classes and labels to buttons + var c = this.container; + $.each(this.buttonClasses, function (idx, val) { + c.find('button').addClass(val); + }); + this.container.find('.daterangepicker_start_input label').html(this.locale.fromLabel); + this.container.find('.daterangepicker_end_input label').html(this.locale.toLabel); + if (this.applyClass.length) + this.container.find('.applyBtn').addClass(this.applyClass); + if (this.cancelClass.length) + this.container.find('.cancelBtn').addClass(this.cancelClass); + this.container.find('.applyBtn').html(this.locale.applyLabel); + this.container.find('.cancelBtn').html(this.locale.cancelLabel); + + //event listeners + + this.container.find('.calendar') + .on('click.daterangepicker', '.prev', $.proxy(this.clickPrev, this)) + .on('click.daterangepicker', '.next', $.proxy(this.clickNext, this)) + .on('click.daterangepicker', 'td.available', $.proxy(this.clickDate, this)) + .on('mouseenter.daterangepicker', 'td.available', $.proxy(this.hoverDate, this)) + .on('mouseleave.daterangepicker', 'td.available', $.proxy(this.updateFormInputs, this)) + .on('change.daterangepicker', 'select.yearselect', $.proxy(this.updateMonthYear, this)) + .on('change.daterangepicker', 'select.monthselect', $.proxy(this.updateMonthYear, this)) + .on('change.daterangepicker', 'select.hourselect,select.minuteselect,select.secondselect,select.ampmselect', $.proxy(this.updateTime, this)); + + this.container.find('.ranges') + .on('click.daterangepicker', 'button.applyBtn', $.proxy(this.clickApply, this)) + .on('click.daterangepicker', 'button.cancelBtn', $.proxy(this.clickCancel, this)) + .on('click.daterangepicker', '.daterangepicker_start_input,.daterangepicker_end_input', $.proxy(this.showCalendars, this)) + .on('change.daterangepicker', '.daterangepicker_start_input,.daterangepicker_end_input', $.proxy(this.inputsChanged, this)) + .on('keydown.daterangepicker', '.daterangepicker_start_input,.daterangepicker_end_input', $.proxy(this.inputsKeydown, this)) + .on('click.daterangepicker', 'li', $.proxy(this.clickRange, this)) + .on('mouseenter.daterangepicker', 'li', $.proxy(this.enterRange, this)) + .on('mouseleave.daterangepicker', 'li', $.proxy(this.updateFormInputs, this)); + + if (this.element.is('input')) { + this.element.on({ + 'click.daterangepicker': $.proxy(this.show, this), + 'focus.daterangepicker': $.proxy(this.show, this), + 'keyup.daterangepicker': $.proxy(this.updateFromControl, this) + }); + } else { + this.element.on('click.daterangepicker', $.proxy(this.toggle, this)); + } + + }; + + DateRangePicker.prototype = { + + constructor: DateRangePicker, + + setOptions: function(options, callback) { + + this.startDate = moment().startOf('day'); + this.endDate = moment().endOf('day'); + this.timeZone = moment().zone(); + this.minDate = false; + this.maxDate = false; + this.dateLimit = false; + + this.showDropdowns = false; + this.showWeekNumbers = false; + this.timePicker = false; + this.timePickerSeconds = false; + this.timePickerIncrement = 30; + this.timePicker12Hour = true; + this.singleDatePicker = false; + this.ranges = {}; + + this.opens = 'right'; + if (this.element.hasClass('pull-right')) + this.opens = 'left'; + + this.buttonClasses = ['btn', 'btn-small btn-sm']; + this.applyClass = 'btn-success'; + this.cancelClass = 'btn-default'; + + this.format = 'MM/DD/YYYY'; + this.separator = ' - '; + + this.locale = { + applyLabel: 'Apply', + cancelLabel: 'Cancel', + fromLabel: 'From', + toLabel: 'To', + weekLabel: 'W', + customRangeLabel: 'Custom Range', + daysOfWeek: moment.weekdaysMin(), + monthNames: moment.monthsShort(), + firstDay: moment.localeData()._week.dow + }; + + this.cb = function () { }; + + if (typeof options.format === 'string') this.format = options.format; - if (typeof options.separator == 'string') + if (typeof options.separator === 'string') this.separator = options.separator; - if (typeof options.startDate == 'string') + if (typeof options.startDate === 'string') this.startDate = moment(options.startDate, this.format); - if (typeof options.endDate == 'string') + if (typeof options.endDate === 'string') this.endDate = moment(options.endDate, this.format); - if (typeof options.minDate == 'string') + if (typeof options.minDate === 'string') this.minDate = moment(options.minDate, this.format); - if (typeof options.maxDate == 'string') + if (typeof options.maxDate === 'string') this.maxDate = moment(options.maxDate, this.format); - if (typeof options.startDate == 'object') + if (typeof options.startDate === 'object') this.startDate = moment(options.startDate); - if (typeof options.endDate == 'object') + if (typeof options.endDate === 'object') this.endDate = moment(options.endDate); - if (typeof options.minDate == 'object') + if (typeof options.minDate === 'object') this.minDate = moment(options.minDate); - if (typeof options.maxDate == 'object') + if (typeof options.maxDate === 'object') this.maxDate = moment(options.maxDate); - if (typeof options.ranges == 'object') { - for (var range in options.ranges) { + if (typeof options.applyClass === 'string') + this.applyClass = options.applyClass; - var start = moment(options.ranges[range][0]); - var end = moment(options.ranges[range][1]); + if (typeof options.cancelClass === 'string') + this.cancelClass = options.cancelClass; + + if (typeof options.dateLimit === 'object') + this.dateLimit = options.dateLimit; + + if (typeof options.locale === 'object') { + + if (typeof options.locale.daysOfWeek === 'object') { + // Create a copy of daysOfWeek to avoid modification of original + // options object for reusability in multiple daterangepicker instances + this.locale.daysOfWeek = options.locale.daysOfWeek.slice(); + } + + if (typeof options.locale.monthNames === 'object') { + this.locale.monthNames = options.locale.monthNames.slice(); + } + + if (typeof options.locale.firstDay === 'number') { + this.locale.firstDay = options.locale.firstDay; + } + + if (typeof options.locale.applyLabel === 'string') { + this.locale.applyLabel = options.locale.applyLabel; + } + + if (typeof options.locale.cancelLabel === 'string') { + this.locale.cancelLabel = options.locale.cancelLabel; + } + + if (typeof options.locale.fromLabel === 'string') { + this.locale.fromLabel = options.locale.fromLabel; + } + + if (typeof options.locale.toLabel === 'string') { + this.locale.toLabel = options.locale.toLabel; + } + + if (typeof options.locale.weekLabel === 'string') { + this.locale.weekLabel = options.locale.weekLabel; + } + + if (typeof options.locale.customRangeLabel === 'string') { + this.locale.customRangeLabel = options.locale.customRangeLabel; + } + } + + if (typeof options.opens === 'string') + this.opens = options.opens; + + if (typeof options.showWeekNumbers === 'boolean') { + this.showWeekNumbers = options.showWeekNumbers; + } + + if (typeof options.buttonClasses === 'string') { + this.buttonClasses = [options.buttonClasses]; + } + + if (typeof options.buttonClasses === 'object') { + this.buttonClasses = options.buttonClasses; + } + + if (typeof options.showDropdowns === 'boolean') { + this.showDropdowns = options.showDropdowns; + } + + if (typeof options.singleDatePicker === 'boolean') { + this.singleDatePicker = options.singleDatePicker; + if (this.singleDatePicker) { + this.endDate = this.startDate.clone(); + } + } + + if (typeof options.timePicker === 'boolean') { + this.timePicker = options.timePicker; + } + + if (typeof options.timePickerSeconds === 'boolean') { + this.timePickerSeconds = options.timePickerSeconds; + } + + if (typeof options.timePickerIncrement === 'number') { + this.timePickerIncrement = options.timePickerIncrement; + } + + if (typeof options.timePicker12Hour === 'boolean') { + this.timePicker12Hour = options.timePicker12Hour; + } + + // update day names order to firstDay + if (this.locale.firstDay != 0) { + var iterator = this.locale.firstDay; + while (iterator > 0) { + this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift()); + iterator--; + } + } + + var start, end, range; + + //if no start/end dates set, check if an input element contains initial values + if (typeof options.startDate === 'undefined' && typeof options.endDate === 'undefined') { + if ($(this.element).is('input[type=text]')) { + var val = $(this.element).val(), + split = val.split(this.separator); + + start = end = null; + + if (split.length == 2) { + start = moment(split[0], this.format); + end = moment(split[1], this.format); + } else if (this.singleDatePicker && val !== "") { + start = moment(val, this.format); + end = moment(val, this.format); + } + if (start !== null && end !== null) { + this.startDate = start; + this.endDate = end; + } + } + } + + // bind the time zone used to build the calendar to either the timeZone passed in through the options or the zone of the startDate (which will be the local time zone by default) + if (typeof options.timeZone === 'string' || typeof options.timeZone === 'number') { + this.timeZone = options.timeZone; + this.startDate.zone(this.timeZone); + this.endDate.zone(this.timeZone); + } else { + this.timeZone = moment(this.startDate).zone(); + } + + if (typeof options.ranges === 'object') { + for (range in options.ranges) { + + if (typeof options.ranges[range][0] === 'string') + start = moment(options.ranges[range][0], this.format); + else + start = moment(options.ranges[range][0]); + + if (typeof options.ranges[range][1] === 'string') + end = moment(options.ranges[range][1], this.format); + else + end = moment(options.ranges[range][1]); // If we have a min/max date set, bound this range // to it, but only if it would otherwise fall @@ -173,169 +365,123 @@ } var list = '
    '; - for (var range in this.ranges) { + for (range in this.ranges) { list += '
  • ' + range + '
  • '; } list += '
  • ' + this.locale.customRangeLabel + '
  • '; list += '
'; + this.container.find('.ranges ul').remove(); this.container.find('.ranges').prepend(list); } - if (typeof options.dateLimit == 'object') - this.dateLimit = options.dateLimit; + if (typeof callback === 'function') { + this.cb = callback; + } - // update day names order to firstDay - if (typeof options.locale == 'object') { + if (!this.timePicker) { + this.startDate = this.startDate.startOf('day'); + this.endDate = this.endDate.endOf('day'); + } - if (typeof options.locale.daysOfWeek == 'object') { + if (this.singleDatePicker) { + this.opens = 'right'; + this.container.addClass('single'); + this.container.find('.calendar.right').show(); + this.container.find('.calendar.left').hide(); + if (!this.timePicker) { + this.container.find('.ranges').hide(); + } else { + this.container.find('.ranges .daterangepicker_start_input, .ranges .daterangepicker_end_input').hide(); + } + if (!this.container.find('.calendar.right').hasClass('single')) + this.container.find('.calendar.right').addClass('single'); + } else { + this.container.removeClass('single'); + this.container.find('.calendar.right').removeClass('single'); + this.container.find('.ranges').show(); + } - // Create a copy of daysOfWeek to avoid modification of original - // options object for reusability in multiple daterangepicker instances - this.locale.daysOfWeek = options.locale.daysOfWeek.slice(); + this.oldStartDate = this.startDate.clone(); + this.oldEndDate = this.endDate.clone(); + this.oldChosenLabel = this.chosenLabel; + + this.leftCalendar = { + month: moment([this.startDate.year(), this.startDate.month(), 1, this.startDate.hour(), this.startDate.minute(), this.startDate.second()]), + calendar: [] + }; + + this.rightCalendar = { + month: moment([this.endDate.year(), this.endDate.month(), 1, this.endDate.hour(), this.endDate.minute(), this.endDate.second()]), + calendar: [] + }; + + if (this.opens == 'right' || this.opens == 'center') { + //swap calendar positions + var first = this.container.find('.calendar.first'); + var second = this.container.find('.calendar.second'); + + if (second.hasClass('single')) { + second.removeClass('single'); + first.addClass('single'); } - if (typeof options.locale.firstDay == 'number') { - this.locale.firstDay = options.locale.firstDay; - var iterator = options.locale.firstDay; - while (iterator > 0) { - this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift()); - iterator--; - } + first.removeClass('left').addClass('right'); + second.removeClass('right').addClass('left'); + + if (this.singleDatePicker) { + first.show(); + second.hide(); } } - if (typeof options.opens == 'string') - this.opens = options.opens; - - if (typeof options.showWeekNumbers == 'boolean') { - this.showWeekNumbers = options.showWeekNumbers; + if (typeof options.ranges === 'undefined' && !this.singleDatePicker) { + this.container.addClass('show-calendar'); } - if (typeof options.buttonClasses == 'string') { - this.buttonClasses = [options.buttonClasses]; - } + this.container.addClass('opens' + this.opens); - if (typeof options.buttonClasses == 'object') { - this.buttonClasses = options.buttonClasses; - } + this.updateView(); + this.updateCalendars(); - if (typeof options.showDropdowns == 'boolean') { - this.showDropdowns = options.showDropdowns; - } + }, - if (typeof options.timePicker == 'boolean') { - this.timePicker = options.timePicker; - } + setStartDate: function(startDate) { + if (typeof startDate === 'string') + this.startDate = moment(startDate, this.format).zone(this.timeZone); - if (typeof options.timePickerIncrement == 'number') { - this.timePickerIncrement = options.timePickerIncrement; - } + if (typeof startDate === 'object') + this.startDate = moment(startDate); - if (typeof options.timePicker12Hour == 'boolean') { - this.timePicker12Hour = options.timePicker12Hour; - } + if (!this.timePicker) + this.startDate = this.startDate.startOf('day'); - } + this.oldStartDate = this.startDate.clone(); - if (!this.timePicker) { - this.startDate = this.startDate.startOf('day'); - this.endDate = this.endDate.startOf('day'); - } + this.updateView(); + this.updateCalendars(); + this.updateInputText(); + }, - //apply CSS classes to buttons - var c = this.container; - $.each(this.buttonClasses, function (idx, val) { - c.find('button').addClass(val); - }); + setEndDate: function(endDate) { + if (typeof endDate === 'string') + this.endDate = moment(endDate, this.format).zone(this.timeZone); - if (this.opens == 'right') { - //swap calendar positions - var left = this.container.find('.calendar.left'); - var right = this.container.find('.calendar.right'); - left.removeClass('left').addClass('right'); - right.removeClass('right').addClass('left'); - } + if (typeof endDate === 'object') + this.endDate = moment(endDate); - if (typeof options == 'undefined' || typeof options.ranges == 'undefined') { - this.container.find('.calendar').show(); - this.move(); - } + if (!this.timePicker) + this.endDate = this.endDate.endOf('day'); - if (typeof cb == 'function') - this.cb = cb; + this.oldEndDate = this.endDate.clone(); - this.container.addClass('opens' + this.opens); - - //try parse date if in text input - if (!hasOptions || (typeof options.startDate == 'undefined' && typeof options.endDate == 'undefined')) { - if ($(this.element).is('input[type=text]')) { - var val = $(this.element).val(); - var split = val.split(this.separator); - var start, end; - if (split.length == 2) { - start = moment(split[0], this.format); - end = moment(split[1], this.format); - } - if (start != null && end != null) { - this.startDate = start; - this.endDate = end; - } - } - } - - //state - this.oldStartDate = this.startDate.clone(); - this.oldEndDate = this.endDate.clone(); - - this.leftCalendar = { - month: moment([this.startDate.year(), this.startDate.month(), 1, this.startDate.hour(), this.startDate.minute()]), - calendar: [] - }; - - this.rightCalendar = { - month: moment([this.endDate.year(), this.endDate.month(), 1, this.endDate.hour(), this.endDate.minute()]), - calendar: [] - }; - - //event listeners - this.container.on('mousedown', $.proxy(this.mousedown, this)); - - this.container.find('.calendar') - .on('click', '.prev', $.proxy(this.clickPrev, this)) - .on('click', '.next', $.proxy(this.clickNext, this)) - .on('click', 'td.available', $.proxy(this.clickDate, this)) - .on('mouseenter', 'td.available', $.proxy(this.enterDate, this)) - .on('mouseleave', 'td.available', $.proxy(this.updateFormInputs, this)) - .on('change', 'select.yearselect', $.proxy(this.updateMonthYear, this)) - .on('change', 'select.monthselect', $.proxy(this.updateMonthYear, this)) - .on('change', 'select.hourselect,select.minuteselect,select.ampmselect', $.proxy(this.updateTime, this)); - - this.container.find('.ranges') - .on('click', 'button.applyBtn', $.proxy(this.clickApply, this)) - .on('click', 'button.cancelBtn', $.proxy(this.clickCancel, this)) - .on('click', '.daterangepicker_start_input,.daterangepicker_end_input', $.proxy(this.showCalendars, this)) - .on('click', 'li', $.proxy(this.clickRange, this)) - .on('mouseenter', 'li', $.proxy(this.enterRange, this)) - .on('mouseleave', 'li', $.proxy(this.updateFormInputs, this)); - - this.element.on('keyup', $.proxy(this.updateFromControl, this)); - - this.updateView(); - this.updateCalendars(); - - }; - - DateRangePicker.prototype = { - - constructor: DateRangePicker, - - mousedown: function (e) { - e.stopPropagation(); + this.updateView(); + this.updateCalendars(); + this.updateInputText(); }, updateView: function () { - this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()); - this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()); + this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()).hour(this.startDate.hour()).minute(this.startDate.minute()); + this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()).hour(this.endDate.hour()).minute(this.endDate.minute()); this.updateFormInputs(); }, @@ -354,11 +500,20 @@ if (!this.element.is('input')) return; if (!this.element.val().length) return; - var dateString = this.element.val().split(this.separator); - var start = moment(dateString[0], this.format); - var end = moment(dateString[1], this.format); + var dateString = this.element.val().split(this.separator), + start = null, + end = null; + + if(dateString.length === 2) { + start = moment(dateString[0], this.format).zone(this.timeZone); + end = moment(dateString[1], this.format).zone(this.timeZone); + } + + if (this.singleDatePicker || start === null || end === null) { + start = moment(this.element.val(), this.format).zone(this.timeZone); + end = start; + } - if (start == null || end == null) return; if (end.isBefore(start)) return; this.oldStartDate = this.startDate.clone(); @@ -375,18 +530,24 @@ notify: function () { this.updateView(); - this.cb(this.startDate, this.endDate); + this.cb(this.startDate, this.endDate, this.chosenLabel); }, move: function () { - var parentOffset = { - top: this.parentEl.offset().top - (this.parentEl.is('body') ? 0 : this.parentEl.scrollTop()), - left: this.parentEl.offset().left - (this.parentEl.is('body') ? 0 : this.parentEl.scrollLeft()) - }; + var parentOffset = { top: 0, left: 0 }; + var parentRightEdge = $(window).width(); + if (!this.parentEl.is('body')) { + parentOffset = { + top: this.parentEl.offset().top - this.parentEl.scrollTop(), + left: this.parentEl.offset().left - this.parentEl.scrollLeft() + }; + parentRightEdge = this.parentEl[0].clientWidth + this.parentEl.offset().left; + } + if (this.opens == 'left') { this.container.css({ top: this.element.offset().top + this.element.outerHeight() - parentOffset.top, - right: $(window).width() - this.element.offset().left - this.element.outerWidth() - parentOffset.left, + right: parentRightEdge - this.element.offset().left - this.element.outerWidth(), left: 'auto' }); if (this.container.offset().left < 0) { @@ -395,6 +556,19 @@ left: 9 }); } + } else if (this.opens == 'center') { + this.container.css({ + top: this.element.offset().top + this.element.outerHeight() - parentOffset.top, + left: this.element.offset().left - parentOffset.left + this.element.outerWidth() / 2 + - this.container.outerWidth() / 2, + right: 'auto' + }); + if (this.container.offset().left < 0) { + this.container.css({ + right: 'auto', + left: 9 + }); + } } else { this.container.css({ top: this.element.offset().top + this.element.outerHeight() - parentOffset.top, @@ -410,20 +584,58 @@ } }, + toggle: function (e) { + if (this.element.hasClass('active')) { + this.hide(); + } else { + this.show(); + } + }, + show: function (e) { + if (this.isShowing) return; + + this.element.addClass('active'); this.container.show(); this.move(); - if (e) { - e.stopPropagation(); - e.preventDefault(); - } + // Create a click proxy that is private to this instance of datepicker, for unbinding + this._outsideClickProxy = $.proxy(function (e) { this.outsideClick(e); }, this); + // Bind global datepicker mousedown for hiding and + $(document) + .on('mousedown.daterangepicker', this._outsideClickProxy) + // also support mobile devices + .on('touchend.daterangepicker', this._outsideClickProxy) + // also explicitly play nice with Bootstrap dropdowns, which stopPropagation when clicking them + .on('click.daterangepicker', '[data-toggle=dropdown]', this._outsideClickProxy) + // and also close when focus changes to outside the picker (eg. tabbing between controls) + .on('focusin.daterangepicker', this._outsideClickProxy); - $(document).on('mousedown', $.proxy(this.hide, this)); - this.element.trigger('shown', {target: e.target, picker: this}); + this.isShowing = true; + this.element.trigger('show.daterangepicker', this); + }, + + outsideClick: function (e) { + var target = $(e.target); + // if the page is clicked anywhere except within the daterangerpicker/button + // itself then call this.hide() + if ( + // ie modal dialog fix + e.type == "focusin" || + target.closest(this.element).length || + target.closest(this.container).length || + target.closest('.calendar-date').length + ) return; + this.hide(); }, hide: function (e) { + if (!this.isShowing) return; + + $(document) + .off('.daterangepicker'); + + this.element.removeClass('active'); this.container.hide(); if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate)) @@ -432,11 +644,12 @@ this.oldStartDate = this.startDate.clone(); this.oldEndDate = this.endDate.clone(); - $(document).off('mousedown', this.hide); - this.element.trigger('hidden', { picker: this }); + this.isShowing = false; + this.element.trigger('hide.daterangepicker', this); }, enterRange: function (e) { + // mouse pointer has entered a range label var label = e.target.innerHTML; if (label == this.locale.customRangeLabel) { this.updateView(); @@ -448,17 +661,51 @@ }, showCalendars: function() { - this.container.find('.calendar').show(); + this.container.addClass('show-calendar'); this.move(); + this.element.trigger('showCalendar.daterangepicker', this); + }, + + hideCalendars: function() { + this.container.removeClass('show-calendar'); + this.element.trigger('hideCalendar.daterangepicker', this); + }, + + // when a date is typed into the start to end date textboxes + inputsChanged: function (e) { + var el = $(e.target); + var date = moment(el.val(), this.format); + if (!date.isValid()) return; + + var startDate, endDate; + if (el.attr('name') === 'daterangepicker_start') { + startDate = date; + endDate = this.endDate; + } else { + startDate = this.startDate; + endDate = date; + } + this.setCustomDates(startDate, endDate); + }, + + inputsKeydown: function(e) { + if (e.keyCode === 13) { + this.inputsChanged(e); + this.notify(); + } }, updateInputText: function() { - if (this.element.is('input')) + if (this.element.is('input') && !this.singleDatePicker) { this.element.val(this.startDate.format(this.format) + this.separator + this.endDate.format(this.format)); + } else if (this.element.is('input')) { + this.element.val(this.endDate.format(this.format)); + } }, clickRange: function (e) { var label = e.target.innerHTML; + this.chosenLabel = label; if (label == this.locale.customRangeLabel) { this.showCalendars(); } else { @@ -469,7 +716,7 @@ if (!this.timePicker) { this.startDate.startOf('day'); - this.endDate.startOf('day'); + this.endDate.endOf('day'); } this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()).hour(this.startDate.hour()).minute(this.startDate.minute()); @@ -478,17 +725,18 @@ this.updateInputText(); - this.container.find('.calendar').hide(); + this.hideCalendars(); this.hide(); + this.element.trigger('apply.daterangepicker', this); } }, clickPrev: function (e) { var cal = $(e.target).parents('.calendar'); if (cal.hasClass('left')) { - this.leftCalendar.month.subtract('month', 1); + this.leftCalendar.month.subtract(1, 'month'); } else { - this.rightCalendar.month.subtract('month', 1); + this.rightCalendar.month.subtract(1, 'month'); } this.updateCalendars(); }, @@ -496,15 +744,14 @@ clickNext: function (e) { var cal = $(e.target).parents('.calendar'); if (cal.hasClass('left')) { - this.leftCalendar.month.add('month', 1); + this.leftCalendar.month.add(1, 'month'); } else { - this.rightCalendar.month.add('month', 1); + this.rightCalendar.month.add(1, 'month'); } this.updateCalendars(); }, - enterDate: function (e) { - + hoverDate: function (e) { var title = $(e.target).attr('data-title'); var row = title.substr(1, 1); var col = title.substr(3, 1); @@ -515,7 +762,19 @@ } else { this.container.find('input[name=daterangepicker_end]').val(this.rightCalendar.calendar[row][col].format(this.format)); } + }, + setCustomDates: function(startDate, endDate) { + this.chosenLabel = this.locale.customRangeLabel; + if (startDate.isAfter(endDate)) { + var difference = this.endDate.diff(this.startDate); + endDate = moment(startDate).add(difference, 'ms'); + } + this.startDate = startDate; + this.endDate = endDate; + + this.updateView(); + this.updateCalendars(); }, clickDate: function (e) { @@ -524,19 +783,20 @@ var col = title.substr(3, 1); var cal = $(e.target).parents('.calendar'); + var startDate, endDate; if (cal.hasClass('left')) { - var startDate = this.leftCalendar.calendar[row][col]; - var endDate = this.endDate; - if (typeof this.dateLimit == 'object') { + startDate = this.leftCalendar.calendar[row][col]; + endDate = this.endDate; + if (typeof this.dateLimit === 'object') { var maxDate = moment(startDate).add(this.dateLimit).startOf('day'); if (endDate.isAfter(maxDate)) { endDate = maxDate; } } } else { - var startDate = this.startDate; - var endDate = this.rightCalendar.calendar[row][col]; - if (typeof this.dateLimit == 'object') { + startDate = this.startDate; + endDate = this.rightCalendar.calendar[row][col]; + if (typeof this.dateLimit === 'object') { var minDate = moment(endDate).subtract(this.dateLimit).startOf('day'); if (startDate.isBefore(minDate)) { startDate = minDate; @@ -544,72 +804,72 @@ } } - cal.find('td').removeClass('active'); - - if (startDate.isSame(endDate) || startDate.isBefore(endDate)) { - $(e.target).addClass('active'); - this.startDate = startDate; - this.endDate = endDate; - } else if (startDate.isAfter(endDate)) { - $(e.target).addClass('active'); - this.startDate = startDate; - this.endDate = moment(startDate).add('day', 1).startOf('day'); + if (this.singleDatePicker && cal.hasClass('left')) { + endDate = startDate.clone(); + } else if (this.singleDatePicker && cal.hasClass('right')) { + startDate = endDate.clone(); } - this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()); - this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()); - this.updateCalendars(); + cal.find('td').removeClass('active'); + + $(e.target).addClass('active'); + + this.setCustomDates(startDate, endDate); + + if (!this.timePicker) + endDate.endOf('day'); + + if (this.singleDatePicker && !this.timePicker) + this.clickApply(); }, clickApply: function (e) { this.updateInputText(); this.hide(); + this.element.trigger('apply.daterangepicker', this); }, clickCancel: function (e) { this.startDate = this.oldStartDate; this.endDate = this.oldEndDate; + this.chosenLabel = this.oldChosenLabel; this.updateView(); this.updateCalendars(); this.hide(); + this.element.trigger('cancel.daterangepicker', this); }, updateMonthYear: function (e) { - - var isLeft = $(e.target).closest('.calendar').hasClass('left'); - var cal = this.container.find('.calendar.left'); - if (!isLeft) - cal = this.container.find('.calendar.right'); + var isLeft = $(e.target).closest('.calendar').hasClass('left'), + leftOrRight = isLeft ? 'left' : 'right', + cal = this.container.find('.calendar.'+leftOrRight); // Month must be Number for new moment versions var month = parseInt(cal.find('.monthselect').val(), 10); var year = cal.find('.yearselect').val(); - if (isLeft) { - this.leftCalendar.month.month(month).year(year); - } else { - this.rightCalendar.month.month(month).year(year); - } - + this[leftOrRight+'Calendar'].month.month(month).year(year); this.updateCalendars(); - }, updateTime: function(e) { - var isLeft = $(e.target).closest('.calendar').hasClass('left'); - var cal = this.container.find('.calendar.left'); - if (!isLeft) - cal = this.container.find('.calendar.right'); + var cal = $(e.target).closest('.calendar'), + isLeft = cal.hasClass('left'); - var hour = parseInt(cal.find('.hourselect').val()); - var minute = parseInt(cal.find('.minuteselect').val()); + var hour = parseInt(cal.find('.hourselect').val(), 10); + var minute = parseInt(cal.find('.minuteselect').val(), 10); + var second = 0; + + if (this.timePickerSeconds) { + second = parseInt(cal.find('.secondselect').val(), 10); + } if (this.timePicker12Hour) { var ampm = cal.find('.ampmselect').val(); - if (ampm == 'PM' && hour < 12) + if (ampm === 'PM' && hour < 12) hour += 12; - if (ampm == 'AM' && hour == 12) + if (ampm === 'AM' && hour === 12) hour = 0; } @@ -617,25 +877,31 @@ var start = this.startDate.clone(); start.hour(hour); start.minute(minute); + start.second(second); this.startDate = start; - this.leftCalendar.month.hour(hour).minute(minute); + this.leftCalendar.month.hour(hour).minute(minute).second(second); + if (this.singleDatePicker) + this.endDate = start.clone(); } else { var end = this.endDate.clone(); end.hour(hour); end.minute(minute); + end.second(second); this.endDate = end; - this.rightCalendar.month.hour(hour).minute(minute); + if (this.singleDatePicker) + this.startDate = end.clone(); + this.rightCalendar.month.hour(hour).minute(minute).second(second); } + this.updateView(); this.updateCalendars(); - }, updateCalendars: function () { - this.leftCalendar.calendar = this.buildCalendar(this.leftCalendar.month.month(), this.leftCalendar.month.year(), this.leftCalendar.month.hour(), this.leftCalendar.month.minute(), 'left'); - this.rightCalendar.calendar = this.buildCalendar(this.rightCalendar.month.month(), this.rightCalendar.month.year(), this.rightCalendar.month.hour(), this.rightCalendar.month.minute(), 'right'); - this.container.find('.calendar.left').html(this.renderCalendar(this.leftCalendar.calendar, this.startDate, this.minDate, this.maxDate)); - this.container.find('.calendar.right').html(this.renderCalendar(this.rightCalendar.calendar, this.endDate, this.startDate, this.maxDate)); + this.leftCalendar.calendar = this.buildCalendar(this.leftCalendar.month.month(), this.leftCalendar.month.year(), this.leftCalendar.month.hour(), this.leftCalendar.month.minute(), this.leftCalendar.month.second(), 'left'); + this.rightCalendar.calendar = this.buildCalendar(this.rightCalendar.month.month(), this.rightCalendar.month.year(), this.rightCalendar.month.hour(), this.rightCalendar.month.minute(), this.rightCalendar.month.second(), 'right'); + this.container.find('.calendar.left').empty().html(this.renderCalendar(this.leftCalendar.calendar, this.startDate, this.minDate, this.maxDate, 'left')); + this.container.find('.calendar.right').empty().html(this.renderCalendar(this.rightCalendar.calendar, this.endDate, this.singleDatePicker ? this.minDate : this.startDate, this.maxDate, 'right')); this.container.find('.ranges li').removeClass('active'); var customRange = true; @@ -644,34 +910,44 @@ if (this.timePicker) { if (this.startDate.isSame(this.ranges[range][0]) && this.endDate.isSame(this.ranges[range][1])) { customRange = false; - this.container.find('.ranges li:eq(' + i + ')').addClass('active'); + this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')') + .addClass('active').html(); } } else { //ignore times when comparing dates if time picker is not enabled if (this.startDate.format('YYYY-MM-DD') == this.ranges[range][0].format('YYYY-MM-DD') && this.endDate.format('YYYY-MM-DD') == this.ranges[range][1].format('YYYY-MM-DD')) { customRange = false; - this.container.find('.ranges li:eq(' + i + ')').addClass('active'); + this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')') + .addClass('active').html(); } } i++; } - if (customRange) - this.container.find('.ranges li:last').addClass('active'); + if (customRange) { + this.chosenLabel = this.container.find('.ranges li:last').addClass('active').html(); + this.showCalendars(); + } }, - buildCalendar: function (month, year, hour, minute, side) { - + buildCalendar: function (month, year, hour, minute, second, side) { + var daysInMonth = moment([year, month]).daysInMonth(); var firstDay = moment([year, month, 1]); - var lastMonth = moment(firstDay).subtract('month', 1).month(); - var lastYear = moment(firstDay).subtract('month', 1).year(); + var lastDay = moment([year, month, daysInMonth]); + var lastMonth = moment(firstDay).subtract(1, 'month').month(); + var lastYear = moment(firstDay).subtract(1, 'month').year(); var daysInLastMonth = moment([lastYear, lastMonth]).daysInMonth(); var dayOfWeek = firstDay.day(); + var i; + //initialize a 6 rows x 7 columns array for the calendar var calendar = []; - for (var i = 0; i < 6; i++) { + calendar.firstDay = firstDay; + calendar.lastDay = lastDay; + + for (i = 0; i < 6; i++) { calendar[i] = []; } @@ -683,25 +959,39 @@ if (dayOfWeek == this.locale.firstDay) startDay = daysInLastMonth - 6; - var curDate = moment([lastYear, lastMonth, startDay, 12, minute]); - for (var i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = moment(curDate).add('hour', 24)) { - if (i > 0 && col % 7 == 0) { + var curDate = moment([lastYear, lastMonth, startDay, 12, minute, second]).zone(this.timeZone); + + var col, row; + for (i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = moment(curDate).add(24, 'hour')) { + if (i > 0 && col % 7 === 0) { col = 0; row++; } calendar[row][col] = curDate.clone().hour(hour); curDate.hour(12); + + if (this.minDate && calendar[row][col].format('YYYY-MM-DD') == this.minDate.format('YYYY-MM-DD') && calendar[row][col].isBefore(this.minDate) && side == 'left') { + calendar[row][col] = this.minDate.clone(); + } + + if (this.maxDate && calendar[row][col].format('YYYY-MM-DD') == this.maxDate.format('YYYY-MM-DD') && calendar[row][col].isAfter(this.maxDate) && side == 'right') { + calendar[row][col] = this.maxDate.clone(); + } + } return calendar; - }, renderDropdowns: function (selected, minDate, maxDate) { var currentMonth = selected.month(); + var currentYear = selected.year(); + var maxYear = (maxDate && maxDate.year()) || (currentYear + 5); + var minYear = (minDate && minDate.year()) || (currentYear - 50); + var monthHtml = '"; - var currentYear = selected.year(); - var maxYear = (maxDate && maxDate.year()) || (currentYear + 5); - var minYear = (minDate && minDate.year()) || (currentYear - 50); var yearHtml = ''; + + // Disallow selections before the minDate or after the maxDate + var min_hour = 0; + var max_hour = 23; + + if (minDate && (side == 'left' || this.singleDatePicker) && selected.format('YYYY-MM-DD') == minDate.format('YYYY-MM-DD')) { + min_hour = minDate.hour(); + if (selected.hour() < min_hour) + selected.hour(min_hour); + if (this.timePicker12Hour && min_hour >= 12 && selected.hour() >= 12) + min_hour -= 12; + if (this.timePicker12Hour && min_hour == 12) + min_hour = 1; + } + + if (maxDate && (side == 'right' || this.singleDatePicker) && selected.format('YYYY-MM-DD') == maxDate.format('YYYY-MM-DD')) { + max_hour = maxDate.hour(); + if (selected.hour() > max_hour) + selected.hour(max_hour); + if (this.timePicker12Hour && max_hour >= 12 && selected.hour() >= 12) + max_hour -= 12; + } + var start = 0; var end = 23; var selected_hour = selected.hour(); @@ -822,13 +1133,16 @@ end = 12; if (selected_hour >= 12) selected_hour -= 12; - if (selected_hour == 0) + if (selected_hour === 0) selected_hour = 12; } - for (var i = start; i <= end; i++) { + for (i = start; i <= end; i++) { + if (i == selected_hour) { html += ''; + } else if (i < min_hour || i > max_hour) { + html += ''; } else { html += ''; } @@ -838,12 +1152,30 @@ html += ' '; + if (this.timePickerSeconds) { + html += ': '; + } + if (this.timePicker12Hour) { html += ''; } @@ -867,6 +1229,14 @@ return html; + }, + + remove: function() { + + this.container.remove(); + this.element.off('.daterangepicker'); + this.element.removeData('daterangepicker'); + } }; @@ -874,10 +1244,11 @@ $.fn.daterangepicker = function (options, cb) { this.each(function () { var el = $(this); - if (!el.data('daterangepicker')) - el.data('daterangepicker', new DateRangePicker(el, options, cb)); + if (el.data('daterangepicker')) + el.data('daterangepicker').remove(); + el.data('daterangepicker', new DateRangePicker(el, options, cb)); }); return this; }; -}(window.jQuery); +})); From 0be37b9e8578115728b86644de21b4465504bfd0 Mon Sep 17 00:00:00 2001 From: Adirelle Date: Thu, 8 Jan 2015 17:30:15 +0100 Subject: [PATCH 09/11] Franch translation updates. Closes #733 --- PHPCI/Languages/lang.fr.php | 56 ++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/PHPCI/Languages/lang.fr.php b/PHPCI/Languages/lang.fr.php index bd85bccd..9e7cd339 100644 --- a/PHPCI/Languages/lang.fr.php +++ b/PHPCI/Languages/lang.fr.php @@ -14,21 +14,21 @@ $strings = array( // Log in: 'log_in_to_phpci' => 'Connectez-vous à PHPCI', 'login_error' => 'Adresse email ou mot de passe invalide', - 'forgotten_password_link' => 'Mot de passe oublié ?', + 'forgotten_password_link' => 'Mot de passe oublié ?', 'reset_emailed' => 'Nous vous avons envoyé un email avec un lien pour réinitialiser votre mot de passe.', 'reset_header' => 'Pas d\'inquiétude
Entrez simplement votre adresse email ci-dessous et nous vous enverrons un message avec un lien pour réinitialiser votre mot de passe.', 'reset_email_address' => 'Entrez votre adresse email:', 'reset_send_email' => 'Envoyer le mail', 'reset_enter_password' => 'Veuillez entrer un nouveau mot de passe', - 'reset_new_password' => 'Nouveau mot de passe :', + 'reset_new_password' => 'Nouveau mot de passe :', 'reset_change_password' => 'Modifier le mot de passe', 'reset_no_user_exists' => 'Il n\'existe aucun utilisateur avec cette adresse email, merci de réessayer.', 'reset_email_body' => 'Bonjour %s, Vous avez reçu cet email parce qu\'une demande de réinitialisation de mot de passe a été faite pour votre compte PHPCI. -Si c\'est bien vous, merci de cliquer sur le lien suivant pour réinitialiser votre mot de passe : %ssession/reset-password/%d/%s +Si c\'est bien vous, merci de cliquer sur le lien suivant pour réinitialiser votre mot de passe : %ssession/reset-password/%d/%s Sinon, merci d\'ignorer ce message. @@ -49,9 +49,9 @@ PHPCI', 'n_builds_running' => '%d builds en cours d\'exécution', 'edit_profile' => 'Éditer le profil', 'sign_out' => 'Déconnexion', - 'branch_x' => 'Branche : %s', - 'created_x' => 'Créé à : %s', - 'started_x' => 'Démarré à : %s', + 'branch_x' => 'Branche : %s', + 'created_x' => 'Créé à : %s', + 'started_x' => 'Démarré à : %s', // Sidebar 'hello_name' => 'Salut %s', @@ -67,19 +67,19 @@ PHPCI', 'delete_project' => 'Supprimer le projet', // Project Summary: - 'no_builds_yet' => 'Aucun build pour le moment!', - 'x_of_x_failed' => '%d parmis les derniers %d builds ont échoué.', - 'x_of_x_failed_short' => '%d / %d ont échoué.', - 'last_successful_build' => ' Le dernier build qui a réussi est %s.', - 'never_built_successfully' => ' Aucun build n\'a été exécuté avec succès sur ce projet.', - 'all_builds_passed' => 'Les derniers %d builds ont réussis.', - 'all_builds_passed_short' => '%d / %d ont réussis.', - 'last_failed_build' => ' Le dernier build en échec est %s.', - 'never_failed_build' => ' Ce projet n\'a jamais eu un build en échec.', + 'no_builds_yet' => 'Aucun build pour le moment !', + 'x_of_x_failed' => '%d des %d derniers builds ont échoué.', + 'x_of_x_failed_short' => '%d échecs / %d.', + 'last_successful_build' => ' Le dernier build réussi date du %s.', + 'never_built_successfully' => ' Aucun build de ce projet n\'a réussi.', + 'all_builds_passed' => 'Les %d derniers builds ont réussi.', + 'all_builds_passed_short' => '%d réussites / %d.', + 'last_failed_build' => ' Le dernier build en échec date du %s.', + 'never_failed_build' => ' Aucun build de ce projet n\'a échoué.', 'view_project' => 'Voir le projet', // Timeline: - 'latest_builds' => 'Les derniers Builds', + 'latest_builds' => 'Derniers builds', 'pending' => 'En attente', 'running' => 'En cours', 'success' => 'Terminé', @@ -102,8 +102,8 @@ PHPCI', 'local' => 'Chemin local', 'hg' => 'Mercurial', - 'where_hosted' => 'Où est hébergé votre projet ?', - 'choose_github' => 'Choisissez un dépôt GitHub :', + 'where_hosted' => 'Où est hébergé votre projet ?', + 'choose_github' => 'Choisissez un dépôt GitHub :', 'repo_name' => 'Nom du dépôt / URL (distance) ou chemin (local)', 'project_title' => 'Titre du projet', @@ -112,7 +112,7 @@ PHPCI', 'build_config' => 'Configuration PHPCI spécifique pour ce projet (si vous ne pouvez pas ajouter de fichier phpci.yml à la racine du dépôt)', 'default_branch' => 'Nom de la branche par défaut', - 'allow_public_status' => 'Activer la page de statut publique et l\'image pour ce projet ?', + 'allow_public_status' => 'Activer la page de statut publique et l\'image pour ce projet ?', 'save_project' => 'Enregistrer le projet', 'error_mercurial' => 'Les URLs de dépôt Mercurial doivent commencer par http:// ou https://', @@ -155,7 +155,7 @@ PHPCI', 'committed_by_x' => 'Committé par %s', - 'commit_id_x' => 'Commit: %s', + 'commit_id_x' => 'Commit : %s', 'chart_display' => 'Ce graphique s\'affichera une fois que le build sera terminé.', @@ -207,7 +207,7 @@ PHPCI', 'update_your_details' => 'Mettre à jour vos préférences', 'your_details_updated' => 'Vos préférences ont été bien mises à jour.', 'add_user' => 'Ajouter un utilisateur', - 'is_admin' => 'Est-il administrateur ?', + 'is_admin' => 'Est-il administrateur ?', 'yes' => 'Oui', 'no' => 'Non', 'edit' => 'Éditer', @@ -287,17 +287,17 @@ PHPCI', 'config_path' => 'Chemin vers le fichier de configuration', 'install_phpci' => 'Installer PHPCI', 'welcome_to_phpci' => 'Bienvenue sur PHPCI', - 'please_answer' => 'Merci de répondre aux questions suivantes:', + 'please_answer' => 'Merci de répondre aux questions suivantes :', 'phpci_php_req' => 'PHPCI requiert au moins PHP 5.3.8 pour fonctionner.', - 'extension_required' => 'Extensions requises: %s', + 'extension_required' => 'Extensions requises : %s', 'function_required' => 'PHPCI doit être capable d\'appeler la fonction %s(). Est-ce qu\'elle est désactivée dans votre php.ini?', 'requirements_not_met' => 'PHPCI ne peut pas être installé parce que toutes les conditions requises ne sont pas respectées. Merci de corriger les erreurs ci-dessus avant de continuer.', 'must_be_valid_email' => 'Doit être une adresse email valide.', 'must_be_valid_url' => 'Doit être une URL valide.', - 'enter_name' => 'Nom de l\'admin:', - 'enter_email' => 'Email de l\'admin:', - 'enter_password' => 'Mot de passe de l\'admin:', + 'enter_name' => 'Nom de l\'admin :', + 'enter_email' => 'Email de l\'admin :', + 'enter_password' => 'Mot de passe de l\'admin :', 'enter_phpci_url' => 'Votre URL vers PHPCI (par exemple "http://phpci.local"): ', 'enter_db_host' => 'Merci d\'entrer le nom d\'hôte MySQL [localhost]: ', @@ -306,7 +306,7 @@ PHPCI', 'enter_db_pass' => 'Merci d\'entrer le mot de passe MySQL: ', 'could_not_connect' => 'PHPCI ne peut pas se connecter à MySQL à partir des informations fournies. Veuillez réessayer..', 'setting_up_db' => 'Paramétrage de la base de données... ', - 'user_created' => 'Le compte utilisateur a été créé!', + 'user_created' => 'Le compte utilisateur a été créé !', 'failed_to_create' => 'PHPCI n\'a pas réussi à créer votre compte admin.', 'config_exists' => 'Le fichier de configuration PHPCI existe et n\'est pas vide.', 'update_instead' => 'Si vous essayez de mettre à jour PHPCI, merci d\'utiliser la commande phpci:update.', @@ -365,7 +365,7 @@ PHPCI', 'n_emails_sent' => '%d emails envoyés.', 'n_emails_failed' => '%d emails dont l\'envoi a échoué.', 'unable_to_set_env' => 'Impossible d\'initialiser la variable d\'environnement', - 'tag_created' => 'Tag créé par PHPCI: %s', + 'tag_created' => 'Tag créé par PHPCI : %s', 'x_built_at_x' => '%PROJECT_TITLE% construit à %BUILD_URI%', 'hipchat_settings' => 'Merci de définir une "room" et un "authToken" pour le plugin hipchat_notify', 'irc_settings' => 'Vous devez configurer un serveur, une "room" et un "nick".', From 353c4cafdb3a4f49914821cac2082ebd7f488540 Mon Sep 17 00:00:00 2001 From: Adirelle Date: Thu, 8 Jan 2015 16:48:28 +0100 Subject: [PATCH 10/11] Remove duplicates from the list of recipients in the email plugin. Closes #731 --- PHPCI/Plugin/Email.php | 2 +- Tests/PHPCI/Plugin/EmailTest.php | 57 ++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/PHPCI/Plugin/Email.php b/PHPCI/Plugin/Email.php index 98b226e3..7c5171d6 100644 --- a/PHPCI/Plugin/Email.php +++ b/PHPCI/Plugin/Email.php @@ -182,7 +182,7 @@ class Email implements \PHPCI\Plugin $addresses[] = $this->options['default_mailto_address']; return $addresses; } - return $addresses; + return array_unique($addresses); } /** diff --git a/Tests/PHPCI/Plugin/EmailTest.php b/Tests/PHPCI/Plugin/EmailTest.php index 87225772..0fa2d8f0 100644 --- a/Tests/PHPCI/Plugin/EmailTest.php +++ b/Tests/PHPCI/Plugin/EmailTest.php @@ -57,6 +57,10 @@ class EmailTest extends \PHPUnit_Framework_TestCase ->method('getStatus') ->will($this->returnValue(\PHPCI\Model\Build::STATUS_SUCCESS)); + $this->mockBuild->expects($this->any()) + ->method('getCommitterEmail') + ->will($this->returnValue("committer@test.com")); + $this->mockCiBuilder = $this->getMock( '\PHPCI\Builder', array( @@ -151,8 +155,53 @@ class EmailTest extends \PHPUnit_Framework_TestCase ); $this->assertEquals($expectedReturn, $returnValue); + } + /** + * @covers PHPUnit::execute + */ + public function testExecute_UniqueRecipientsFromWithCommitter() + { + $this->loadEmailPluginWithOptions( + array( + 'addresses' => array('test-receiver@example.com', 'test-receiver2@example.com') + ) + ); + $actualMails = []; + $this->catchMailPassedToSend($actualMails); + + $returnValue = $this->testedEmailPlugin->execute(); + $this->assertTrue($returnValue); + + $this->assertCount(2, $actualMails); + + $actualTos = array(key($actualMails[0]->getTo()), key($actualMails[1]->getTo())); + $this->assertContains('test-receiver@example.com', $actualTos); + $this->assertContains('test-receiver2@example.com', $actualTos); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_UniqueRecipientsWithCommiter() + { + $this->loadEmailPluginWithOptions( + array( + 'commiter' => true, + 'addresses' => array('test-receiver@example.com', 'committer@test.com') + ) + ); + + $actualMails = []; + $this->catchMailPassedToSend($actualMails); + + $returnValue = $this->testedEmailPlugin->execute(); + $this->assertTrue($returnValue); + + $actualTos = array(key($actualMails[0]->getTo()), key($actualMails[1]->getTo())); + $this->assertContains('test-receiver@example.com', $actualTos); + $this->assertContains('committer@test.com', $actualTos); } /** @@ -215,12 +264,16 @@ class EmailTest extends \PHPUnit_Framework_TestCase */ protected function catchMailPassedToSend(&$actualMail) { - $this->mockMailer->expects($this->once()) + $this->mockMailer->expects(is_array($actualMail) ? $this->atLeast(1) : $this->once()) ->method('send') ->will( $this->returnCallback( function ($passedMail) use (&$actualMail) { - $actualMail = $passedMail; + if(is_array($actualMail)) { + $actualMail[] = $passedMail; + } else { + $actualMail = $passedMail; + } return array(); } ) From e531c80718e635c1bb0a838bc9e81aac093f686e Mon Sep 17 00:00:00 2001 From: Daniel Seif Date: Mon, 5 Jan 2015 00:53:40 +0100 Subject: [PATCH 11/11] Reset the status of a duplicated build to avoid strict-mode errors. When using SQL strict mode, mysql would complain that no default value of the status column is set. Setting the status to 0 before duplicating fixes this. Closes #725 --- PHPCI/Service/BuildService.php | 1 + 1 file changed, 1 insertion(+) diff --git a/PHPCI/Service/BuildService.php b/PHPCI/Service/BuildService.php index b4721779..ca2336c8 100644 --- a/PHPCI/Service/BuildService.php +++ b/PHPCI/Service/BuildService.php @@ -100,6 +100,7 @@ class BuildService $build = new Build(); $build->setValues($data); $build->setCreated(new \DateTime()); + $build->setStatus(0); return $this->buildStore->save($build); }