diff --git a/UPGRADING.md b/UPGRADING.md index 15ca7d21..f42a5f2d 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -18,6 +18,14 @@ bin/console doctrine:migrations:migrate There might be version specific tasks that need to be executed before or after these steps, please see below if your updated version is mentioned below. +## [0.8](https://github.com/kevinpapst/kimai2/releases/tag/0.8) (unreleased) + +There was a change introduced regarding the storage of the date-time objects in timesheet records. +Please read this [Pull request](https://github.com/kevinpapst/kimai2/pull/372) carefully before you follow the instructions +to convert the timezones in your existing time records with `bin/console kimai:convert-timezone --help`. + +If you don't do that, you will end up with wrong times in your database. Be especially careful, when you previously imported data from Kimai v1. + ## [0.7](https://github.com/kevinpapst/kimai2/releases/tag/0.7) (2019-01-28) The configuration `kimai.theme.active_warning` was deprecated and should be replaced in your local.yaml, diff --git a/assets/js/kimai.js b/assets/js/kimai.js index 3087f299..a7057a99 100644 --- a/assets/js/kimai.js +++ b/assets/js/kimai.js @@ -5,10 +5,17 @@ * file that was distributed with this source code. */ +/** global: jQuery */ +/** global: moment */ + if (typeof jQuery === 'undefined') { throw new Error('Kimai requires jQuery'); } +if (typeof moment === 'undefined') { + throw new Error('Kimai requires moment.js'); +} + /* kimai * * @type Object diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 1dd08441..c2cc8916 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -17,6 +17,9 @@ doctrine: # With Symfony 3.3, remove the `resolve:` prefix url: '%env(resolve:DATABASE_URL)%' + + types: + datetime: App\Doctrine\UTCDateTimeType orm: auto_generate_proxy_classes: '%kernel.debug%' naming_strategy: doctrine.orm.naming_strategy.underscore diff --git a/config/packages/kimai.yaml b/config/packages/kimai.yaml index 886a02c1..59f46ed8 100644 --- a/config/packages/kimai.yaml +++ b/config/packages/kimai.yaml @@ -47,6 +47,11 @@ kimai: soft_limit: 1 hard_limit: 3 + # Rules that define the Timesheet validation and behaviour + rules: + # whether records in the future can be created + allow_future_times: true + # -------------------------------------------------------------------------------- # Invoice management #invoice: diff --git a/config/services.yaml b/config/services.yaml index a8948b87..d900dfe6 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -105,6 +105,10 @@ services: tags: - { name: form.type_extension, extended_type: Symfony\Bridge\Doctrine\Form\Type\EntityType } + App\Validator\Constraints\TimesheetValidator: + arguments: + $ruleset: "%kimai.timesheet.rules%" + # ================================================================================ # THEME # ================================================================================ diff --git a/public/build/app.js b/public/build/app.js index de7f35d5..a75d8339 100644 --- a/public/build/app.js +++ b/public/build/app.js @@ -70,7 +70,7 @@ if(void 0===e)throw new Error("AdminLTE requires jQuery");+function(e){"use stri * (c) 2013 Damir Sultanov, http://fronteed.com * MIT Licensed */ -!function(e){function t(e,t,a){var r=e[0],o=/er/.test(a)?f:/bl/.test(a)?m:c,s=a==_?{checked:r[c],disabled:r[m],indeterminate:"true"==e.attr(f)||"false"==e.attr(p)}:r[o];if(/^(ch|di|in)/.test(a)&&!s)n(e,o);else if(/^(un|en|de)/.test(a)&&s)i(e,o);else if(a==_)for(var d in s)s[d]?n(e,d,!0):i(e,d,!0);else t&&"toggle"!=a||(t||e[b]("ifClicked"),s?r[g]!==u&&i(e,o):n(e,o))}function n(t,n,a){var _=t[0],y=t.parent(),b=n==c,L=n==f,k=n==m,w=L?p:b?h:"enabled",T=r(t,w+o(_[g])),x=r(t,n+o(_[g]));if(!0!==_[n]){if(!a&&n==c&&_[g]==u&&_.name){var Y=t.closest("form"),S='input[name="'+_.name+'"]';S=Y.length?Y.find(S):e(S),S.each(function(){this!==_&&e(this).data(d)&&i(e(this),n)})}L?(_[n]=!0,_[c]&&i(t,c,"force")):(a||(_[n]=!0),b&&_[f]&&i(t,f,!1)),s(t,b,n,a)}_[m]&&r(t,D,!0)&&y.find("."+l).css(D,"default"),y[v](x||r(t,n)||""),y.attr("role")&&!L&&y.attr("aria-"+(k?m:c),"true"),y[M](T||r(t,w)||"")}function i(e,t,n){var i=e[0],a=e.parent(),d=t==c,u=t==f,_=t==m,y=u?p:d?h:"enabled",b=r(e,y+o(i[g])),L=r(e,t+o(i[g]));!1!==i[t]&&(!u&&n&&"force"!=n||(i[t]=!1),s(e,d,y,n)),!i[m]&&r(e,D,!0)&&a.find("."+l).css(D,"pointer"),a[M](L||r(e,t)||""),a.attr("role")&&!u&&a.attr("aria-"+(_?m:c),"false"),a[v](b||r(e,y)||"")}function a(t,n){t.data(d)&&(t.parent().html(t.attr("style",t.data(d).s||"")),n&&t[b](n),t.off(".i").unwrap(),e(L+'[for="'+t[0].id+'"]').add(t.closest(L)).off(".i"))}function r(e,t,n){if(e.data(d))return e.data(d).o[t+(n?"":"Class")]}function o(e){return e.charAt(0).toUpperCase()+e.slice(1)}function s(e,t,n,i){i||(t&&e[b]("ifToggled"),e[b]("ifChanged")[b]("if"+o(n)))}var d="iCheck",l=d+"-helper",u="radio",c="checked",h="un"+c,m="disabled",p="determinate",f="in"+p,_="update",g="type",y="touchbegin.i touchend.i",v="addClass",M="removeClass",b="trigger",L="label",D="cursor",k=/ipad|iphone|ipod|android|blackberry|windows phone|opera mini|silk/i.test(navigator.userAgent);e.fn[d]=function(r,o){var s='input[type="checkbox"], input[type="'+u+'"]',h=e(),p=function(t){t.each(function(){var t=e(this);h=t.is(s)?h.add(t):h.add(t.find(s))})};if(/^(check|uncheck|toggle|indeterminate|determinate|disable|enable|update|destroy)$/i.test(r))return r=r.toLowerCase(),p(this),h.each(function(){var n=e(this);"destroy"==r?a(n,"ifDestroyed"):t(n,!0,r),e.isFunction(o)&&o()});if("object"!=typeof r&&r)return this;var D=e.extend({checkedClass:c,disabledClass:m,indeterminateClass:f,labelHover:!0},r),w=D.handle,T=D.hoverClass||"hover",x=D.focusClass||"focus",Y=D.activeClass||"active",S=!!D.labelHover,H=D.labelHoverClass||"hover",C=0|(""+D.increaseArea).replace("%","");return"checkbox"!=w&&w!=u||(s='input[type="'+w+'"]'),C<-50&&(C=-50),p(this),h.each(function(){var r=e(this);a(r);var o,s=this,h=s.id,p=-C+"%",f=100+2*C+"%",w={position:"absolute",top:p,left:p,display:"block",width:f,height:f,margin:0,padding:0,background:"#fff",border:0,opacity:0},E=k?{position:"absolute",visibility:"hidden"}:C?w:{position:"absolute",opacity:0},P="checkbox"==s[g]?D.checkboxClass||"icheckbox":D.radioClass||"i"+u,j=e(L+'[for="'+h+'"]').add(r.closest(L)),O=!!D.aria,A=d+"-"+Math.random().toString(36).substr(2,6),I='
")[b]("ifCreated").parent().append(D.insert),o=e('').css(w).appendTo(I),r.data(d,{o:D,s:r.attr("style")}).css(E),!!D.inheritClass&&I[v](s.className||""),!!D.inheritID&&h&&I.attr("id",d+"-"+h),"static"==I.css("position")&&I.css("position","relative"),t(r,!0,_),j.length&&j.on("click.i mouseover.i mouseout.i "+y,function(n){var i=n[g],a=e(this);if(!s[m]){if("click"==i){if(e(n.target).is("a"))return;t(r,!1,!0)}else S&&(/ut|nd/.test(i)?(I[M](T),a[M](H)):(I[v](T),a[v](H)));if(!k)return!1;n.stopPropagation()}}),r.on("click.i focus.i blur.i keyup.i keydown.i keypress.i",function(e){var t=e[g],a=e.keyCode;return"click"!=t&&("keydown"==t&&32==a?(s[g]==u&&s[c]||(s[c]?i(r,c):n(r,c)),!1):void("keyup"==t&&s[g]==u?!s[c]&&n(r,c):/us|ur/.test(t)&&I["blur"==t?M:v](x)))}),o.on("click mousedown mouseup mouseover mouseout "+y,function(e){var n=e[g],i=/wn|up/.test(n)?Y:T;if(!s[m]){if("click"==n?t(r,!1,!0):(/wn|er|in/.test(n)?I[v](i):I[M](i+" "+Y),j.length&&S&&i==T&&j[/ut|nd/.test(n)?M:v](H)),!k)return!1;e.stopPropagation()}})})}}(e||window.Zepto)}).call(t,n("7t+N"))},aM0x:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={1:"১",2:"২",3:"৩",4:"৪",5:"৫",6:"৬",7:"৭",8:"৮",9:"৯",0:"০"},n={"১":"1","২":"2","৩":"3","৪":"4","৫":"5","৬":"6","৭":"7","৮":"8","৯":"9","০":"0"};return e.defineLocale("bn",{months:"জানুয়ারী_ফেব্রুয়ারি_মার্চ_এপ্রিল_মে_জুন_জুলাই_আগস্ট_সেপ্টেম্বর_অক্টোবর_নভেম্বর_ডিসেম্বর".split("_"),monthsShort:"জানু_ফেব_মার্চ_এপ্র_মে_জুন_জুল_আগ_সেপ্ট_অক্টো_নভে_ডিসে".split("_"),weekdays:"রবিবার_সোমবার_মঙ্গলবার_বুধবার_বৃহস্পতিবার_শুক্রবার_শনিবার".split("_"),weekdaysShort:"রবি_সোম_মঙ্গল_বুধ_বৃহস্পতি_শুক্র_শনি".split("_"),weekdaysMin:"রবি_সোম_মঙ্গ_বুধ_বৃহঃ_শুক্র_শনি".split("_"),longDateFormat:{LT:"A h:mm সময়",LTS:"A h:mm:ss সময়",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm সময়",LLLL:"dddd, D MMMM YYYY, A h:mm সময়"},calendar:{sameDay:"[আজ] LT",nextDay:"[আগামীকাল] LT",nextWeek:"dddd, LT",lastDay:"[গতকাল] LT",lastWeek:"[গত] dddd, LT",sameElse:"L"},relativeTime:{future:"%s পরে",past:"%s আগে",s:"কয়েক সেকেন্ড",ss:"%d সেকেন্ড",m:"এক মিনিট",mm:"%d মিনিট",h:"এক ঘন্টা",hh:"%d ঘন্টা",d:"এক দিন",dd:"%d দিন",M:"এক মাস",MM:"%d মাস",y:"এক বছর",yy:"%d বছর"},preparse:function(e){return e.replace(/[১২৩৪৫৬৭৮৯০]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/রাত|সকাল|দুপুর|বিকাল|রাত/,meridiemHour:function(e,t){return 12===e&&(e=0),"রাত"===t&&e>=4||"দুপুর"===t&&e<5||"বিকাল"===t?e+12:e},meridiem:function(e,t,n){return e<4?"রাত":e<10?"সকাল":e<17?"দুপুর":e<20?"বিকাল":"রাত"},week:{dow:0,doy:6}})})},aqvp:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";function t(e,t,n){var i=e+" ";switch(n){case"ss":return i+=1===e?"sekunda":2===e||3===e||4===e?"sekunde":"sekundi";case"m":return t?"jedna minuta":"jedne minute";case"mm":return i+=1===e?"minuta":2===e||3===e||4===e?"minute":"minuta";case"h":return t?"jedan sat":"jednog sata";case"hh":return i+=1===e?"sat":2===e||3===e||4===e?"sata":"sati";case"dd":return i+=1===e?"dan":"dana";case"MM":return i+=1===e?"mjesec":2===e||3===e||4===e?"mjeseca":"mjeseci";case"yy":return i+=1===e?"godina":2===e||3===e||4===e?"godine":"godina"}}return e.defineLocale("bs",{months:"januar_februar_mart_april_maj_juni_juli_august_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj._jun._jul._aug._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sri._čet._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_če_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedjelju] [u] LT";case 3:return"[u] [srijedu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[jučer u] LT",lastWeek:function(){switch(this.day()){case 0:case 3:return"[prošlu] dddd [u] LT";case 6:return"[prošle] [subote] [u] LT";case 1:case 2:case 4:case 5:return"[prošli] dddd [u] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"prije %s",s:"par sekundi",ss:t,m:t,mm:t,h:t,hh:t,d:"dan",dd:t,M:"mjesec",MM:t,y:"godinu",yy:t},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})},bXQP:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("fr-ca",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),monthsParseExact:!0,weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"di_lu_ma_me_je_ve_sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Aujourd’hui à] LT",nextDay:"[Demain à] LT",nextWeek:"dddd [à] LT",lastDay:"[Hier à] LT",lastWeek:"dddd [dernier à] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",ss:"%d secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},dayOfMonthOrdinalParse:/\d{1,2}(er|e)/,ordinal:function(e,t){switch(t){default:case"M":case"Q":case"D":case"DDD":case"d":return e+(1===e?"er":"e");case"w":case"W":return e+(1===e?"re":"e")}}})})},c0dd:function(e,t,n){n("chxu"),n("nyna"),n("olwm"),n("GOEF"),n("vg91"),n("AixB"),n("1QPC"),n("gcLw"),n("nRX6"),n("SAIs"),n("EmE7"),n("mN5h")},c1x4:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={words:{ss:["секунда","секунде","секунди"],m:["један минут","једне минуте"],mm:["минут","минуте","минута"],h:["један сат","једног сата"],hh:["сат","сата","сати"],dd:["дан","дана","дана"],MM:["месец","месеца","месеци"],yy:["година","године","година"]},correctGrammaticalCase:function(e,t){return 1===e?t[0]:e>=2&&e<=4?t[1]:t[2]},translate:function(e,n,i){var a=t.words[i];return 1===i.length?n?a[0]:a[1]:e+" "+t.correctGrammaticalCase(e,a)}};return e.defineLocale("sr-cyrl",{months:"јануар_фебруар_март_април_мај_јун_јул_август_септембар_октобар_новембар_децембар".split("_"),monthsShort:"јан._феб._мар._апр._мај_јун_јул_авг._сеп._окт._нов._дец.".split("_"),monthsParseExact:!0,weekdays:"недеља_понедељак_уторак_среда_четвртак_петак_субота".split("_"),weekdaysShort:"нед._пон._уто._сре._чет._пет._суб.".split("_"),weekdaysMin:"не_по_ут_ср_че_пе_су".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[данас у] LT",nextDay:"[сутра у] LT",nextWeek:function(){switch(this.day()){case 0:return"[у] [недељу] [у] LT";case 3:return"[у] [среду] [у] LT";case 6:return"[у] [суботу] [у] LT";case 1:case 2:case 4:case 5:return"[у] dddd [у] LT"}},lastDay:"[јуче у] LT",lastWeek:function(){return["[прошле] [недеље] [у] LT","[прошлог] [понедељка] [у] LT","[прошлог] [уторка] [у] LT","[прошле] [среде] [у] LT","[прошлог] [четвртка] [у] LT","[прошлог] [петка] [у] LT","[прошле] [суботе] [у] LT"][this.day()]},sameElse:"L"},relativeTime:{future:"за %s",past:"пре %s",s:"неколико секунди",ss:t.translate,m:t.translate,mm:t.translate,h:t.translate,hh:t.translate,d:"дан",dd:t.translate,M:"месец",MM:t.translate,y:"годину",yy:t.translate},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})},chxu:function(e,t,n){(function(e){var t=n("7t+N");e.$=e.jQuery=t,n("z1kw"),n("jf49"),n("DBzq"),n("tNVI");var i=n("PJh5");e.moment=i,n("PL/P"),n("WFwX"),n("R6CI"),n("iES9"),n("FDd9"),e.$.AdminLTE={},e.$.AdminLTE.options={},n("Dehm"),n("l77c"),n("a7jZ"),n("3EEf")}).call(t,n("DuR2"))},dURR:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("ar-ma",{months:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),monthsShort:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),weekdays:"الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[اليوم على الساعة] LT",nextDay:"[غدا على الساعة] LT",nextWeek:"dddd [على الساعة] LT",lastDay:"[أمس على الساعة] LT",lastWeek:"dddd [على الساعة] LT",sameElse:"L"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",ss:"%d ثانية",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"},week:{dow:6,doy:12}})})},dyB6:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("en-nz",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},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"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10;return e+(1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")},week:{dow:1,doy:4}})})},"e/KL":function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("x-pseudo",{months:"J~áñúá~rý_F~ébrú~árý_~Márc~h_Áp~ríl_~Máý_~Júñé~_Júl~ý_Áú~gúst~_Sép~témb~ér_Ó~ctób~ér_Ñ~óvém~bér_~Décé~mbér".split("_"),monthsShort:"J~áñ_~Féb_~Már_~Ápr_~Máý_~Júñ_~Júl_~Áúg_~Sép_~Óct_~Ñóv_~Déc".split("_"),monthsParseExact:!0,weekdays:"S~úñdá~ý_Mó~ñdáý~_Túé~sdáý~_Wéd~ñésd~áý_T~húrs~dáý_~Fríd~áý_S~átúr~dáý".split("_"),weekdaysShort:"S~úñ_~Móñ_~Túé_~Wéd_~Thú_~Frí_~Sát".split("_"),weekdaysMin:"S~ú_Mó~_Tú_~Wé_T~h_Fr~_Sá".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[T~ódá~ý át] LT",nextDay:"[T~ómó~rró~w át] LT",nextWeek:"dddd [át] LT",lastDay:"[Ý~ést~érdá~ý át] LT",lastWeek:"[L~ást] dddd [át] LT",sameElse:"L"},relativeTime:{future:"í~ñ %s",past:"%s á~gó",s:"á ~féw ~sécó~ñds",ss:"%d s~écóñ~ds",m:"á ~míñ~úté",mm:"%d m~íñú~tés",h:"á~ñ hó~úr",hh:"%d h~óúrs",d:"á ~dáý",dd:"%d d~áýs",M:"á ~móñ~th",MM:"%d m~óñt~hs",y:"á ~ýéár",yy:"%d ý~éárs"},dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10;return e+(1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")},week:{dow:1,doy:4}})})},"eBB/":function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("ko",{months:"1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),monthsShort:"1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),weekdays:"일요일_월요일_화요일_수요일_목요일_금요일_토요일".split("_"),weekdaysShort:"일_월_화_수_목_금_토".split("_"),weekdaysMin:"일_월_화_수_목_금_토".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"YYYY.MM.DD.",LL:"YYYY년 MMMM D일",LLL:"YYYY년 MMMM D일 A h:mm",LLLL:"YYYY년 MMMM D일 dddd A h:mm",l:"YYYY.MM.DD.",ll:"YYYY년 MMMM D일",lll:"YYYY년 MMMM D일 A h:mm",llll:"YYYY년 MMMM D일 dddd A h:mm"},calendar:{sameDay:"오늘 LT",nextDay:"내일 LT",nextWeek:"dddd LT",lastDay:"어제 LT",lastWeek:"지난주 dddd LT",sameElse:"L"},relativeTime:{future:"%s 후",past:"%s 전",s:"몇 초",ss:"%d초",m:"1분",mm:"%d분",h:"한 시간",hh:"%d시간",d:"하루",dd:"%d일",M:"한 달",MM:"%d달",y:"일 년",yy:"%d년"},dayOfMonthOrdinalParse:/\d{1,2}(일|월|주)/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"일";case"M":return e+"월";case"w":case"W":return e+"주";default:return e}},meridiemParse:/오전|오후/,isPM:function(e){return"오후"===e},meridiem:function(e,t,n){return e<12?"오전":"오후"}})})},eHwN:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={1:"-inci",5:"-inci",8:"-inci",70:"-inci",80:"-inci",2:"-nci",7:"-nci",20:"-nci",50:"-nci",3:"-üncü",4:"-üncü",100:"-üncü",6:"-ncı",9:"-uncu",10:"-uncu",30:"-uncu",60:"-ıncı",90:"-ıncı"};return e.defineLocale("az",{months:"yanvar_fevral_mart_aprel_may_iyun_iyul_avqust_sentyabr_oktyabr_noyabr_dekabr".split("_"),monthsShort:"yan_fev_mar_apr_may_iyn_iyl_avq_sen_okt_noy_dek".split("_"),weekdays:"Bazar_Bazar ertəsi_Çərşənbə axşamı_Çərşənbə_Cümə axşamı_Cümə_Şənbə".split("_"),weekdaysShort:"Baz_BzE_ÇAx_Çər_CAx_Cüm_Şən".split("_"),weekdaysMin:"Bz_BE_ÇA_Çə_CA_Cü_Şə".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[bugün saat] LT",nextDay:"[sabah saat] LT",nextWeek:"[gələn həftə] dddd [saat] LT",lastDay:"[dünən] LT",lastWeek:"[keçən həftə] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s əvvəl",s:"birneçə saniyə",ss:"%d saniyə",m:"bir dəqiqə",mm:"%d dəqiqə",h:"bir saat",hh:"%d saat",d:"bir gün",dd:"%d gün",M:"bir ay",MM:"%d ay",y:"bir il",yy:"%d il"},meridiemParse:/gecə|səhər|gündüz|axşam/,isPM:function(e){return/^(gündüz|axşam)$/.test(e)},meridiem:function(e,t,n){return e<4?"gecə":e<12?"səhər":e<17?"gündüz":"axşam"},dayOfMonthOrdinalParse:/\d{1,2}-(ıncı|inci|nci|üncü|ncı|uncu)/,ordinal:function(e){if(0===e)return e+"-ıncı";var n=e%10,i=e%100-n,a=e>=100?100:null;return e+(t[n]||t[i]||t[a])},week:{dow:1,doy:7}})})},f4W3:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={words:{ss:["sekunda","sekunde","sekundi"],m:["jedan minut","jedne minute"],mm:["minut","minute","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mesec","meseca","meseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(e,t){return 1===e?t[0]:e>=2&&e<=4?t[1]:t[2]},translate:function(e,n,i){var a=t.words[i];return 1===i.length?n?a[0]:a[1]:e+" "+t.correctGrammaticalCase(e,a)}};return e.defineLocale("sr",{months:"januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedelja_ponedeljak_utorak_sreda_četvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sre._čet._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_če_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedelju] [u] LT";case 3:return"[u] [sredu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[juče u] LT",lastWeek:function(){return["[prošle] [nedelje] [u] LT","[prošlog] [ponedeljka] [u] LT","[prošlog] [utorka] [u] LT","[prošle] [srede] [u] LT","[prošlog] [četvrtka] [u] LT","[prošlog] [petka] [u] LT","[prošle] [subote] [u] LT"][this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"pre %s",s:"nekoliko sekundi",ss:t.translate,m:t.translate,mm:t.translate,h:t.translate,hh:t.translate,d:"dan",dd:t.translate,M:"mesec",MM:t.translate,y:"godinu",yy:t.translate},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})},fW1y:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t=["جنوري","فيبروري","مارچ","اپريل","مئي","جون","جولاءِ","آگسٽ","سيپٽمبر","آڪٽوبر","نومبر","ڊسمبر"],n=["آچر","سومر","اڱارو","اربع","خميس","جمع","ڇنڇر"];return e.defineLocale("sd",{months:t,monthsShort:t,weekdays:n,weekdaysShort:n,weekdaysMin:n,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd، D MMMM YYYY HH:mm"},meridiemParse:/صبح|شام/,isPM:function(e){return"شام"===e},meridiem:function(e,t,n){return e<12?"صبح":"شام"},calendar:{sameDay:"[اڄ] LT",nextDay:"[سڀاڻي] LT",nextWeek:"dddd [اڳين هفتي تي] LT",lastDay:"[ڪالهه] LT",lastWeek:"[گزريل هفتي] dddd [تي] LT",sameElse:"L"},relativeTime:{future:"%s پوء",past:"%s اڳ",s:"چند سيڪنڊ",ss:"%d سيڪنڊ",m:"هڪ منٽ",mm:"%d منٽ",h:"هڪ ڪلاڪ",hh:"%d ڪلاڪ",d:"هڪ ڏينهن",dd:"%d ڏينهن",M:"هڪ مهينو",MM:"%d مهينا",y:"هڪ سال",yy:"%d سال"},preparse:function(e){return e.replace(/،/g,",")},postformat:function(e){return e.replace(/,/g,"،")},week:{dow:1,doy:4}})})},g7KF:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t="jan._feb._mrt._apr._mai_jun._jul._aug._sep._okt._nov._des.".split("_"),n="jan_feb_mrt_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_");return e.defineLocale("fy",{months:"jannewaris_febrewaris_maart_april_maaie_juny_july_augustus_septimber_oktober_novimber_desimber".split("_"),monthsShort:function(e,i){return e?/-MMM-/.test(i)?n[e.month()]:t[e.month()]:t},monthsParseExact:!0,weekdays:"snein_moandei_tiisdei_woansdei_tongersdei_freed_sneon".split("_"),weekdaysShort:"si._mo._ti._wo._to._fr._so.".split("_"),weekdaysMin:"Si_Mo_Ti_Wo_To_Fr_So".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[hjoed om] LT",nextDay:"[moarn om] LT",nextWeek:"dddd [om] LT",lastDay:"[juster om] LT",lastWeek:"[ôfrûne] dddd [om] LT",sameElse:"L"},relativeTime:{future:"oer %s",past:"%s lyn",s:"in pear sekonden",ss:"%d sekonden",m:"ien minút",mm:"%d minuten",h:"ien oere",hh:"%d oeren",d:"ien dei",dd:"%d dagen",M:"ien moanne",MM:"%d moannen",y:"ien jier",yy:"%d jierren"},dayOfMonthOrdinalParse:/\d{1,2}(ste|de)/,ordinal:function(e){return e+(1===e||8===e||e>=20?"ste":"de")},week:{dow:1,doy:4}})})},gEQe:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={1:"೧",2:"೨",3:"೩",4:"೪",5:"೫",6:"೬",7:"೭",8:"೮",9:"೯",0:"೦"},n={"೧":"1","೨":"2","೩":"3","೪":"4","೫":"5","೬":"6","೭":"7","೮":"8","೯":"9","೦":"0"};return e.defineLocale("kn",{months:"ಜನವರಿ_ಫೆಬ್ರವರಿ_ಮಾರ್ಚ್_ಏಪ್ರಿಲ್_ಮೇ_ಜೂನ್_ಜುಲೈ_ಆಗಸ್ಟ್_ಸೆಪ್ಟೆಂಬರ್_ಅಕ್ಟೋಬರ್_ನವೆಂಬರ್_ಡಿಸೆಂಬರ್".split("_"),monthsShort:"ಜನ_ಫೆಬ್ರ_ಮಾರ್ಚ್_ಏಪ್ರಿಲ್_ಮೇ_ಜೂನ್_ಜುಲೈ_ಆಗಸ್ಟ್_ಸೆಪ್ಟೆಂ_ಅಕ್ಟೋ_ನವೆಂ_ಡಿಸೆಂ".split("_"),monthsParseExact:!0,weekdays:"ಭಾನುವಾರ_ಸೋಮವಾರ_ಮಂಗಳವಾರ_ಬುಧವಾರ_ಗುರುವಾರ_ಶುಕ್ರವಾರ_ಶನಿವಾರ".split("_"),weekdaysShort:"ಭಾನು_ಸೋಮ_ಮಂಗಳ_ಬುಧ_ಗುರು_ಶುಕ್ರ_ಶನಿ".split("_"),weekdaysMin:"ಭಾ_ಸೋ_ಮಂ_ಬು_ಗು_ಶು_ಶ".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm",LLLL:"dddd, D MMMM YYYY, A h:mm"},calendar:{sameDay:"[ಇಂದು] LT",nextDay:"[ನಾಳೆ] LT",nextWeek:"dddd, LT",lastDay:"[ನಿನ್ನೆ] LT",lastWeek:"[ಕೊನೆಯ] dddd, LT",sameElse:"L"},relativeTime:{future:"%s ನಂತರ",past:"%s ಹಿಂದೆ",s:"ಕೆಲವು ಕ್ಷಣಗಳು",ss:"%d ಸೆಕೆಂಡುಗಳು",m:"ಒಂದು ನಿಮಿಷ",mm:"%d ನಿಮಿಷ",h:"ಒಂದು ಗಂಟೆ",hh:"%d ಗಂಟೆ",d:"ಒಂದು ದಿನ",dd:"%d ದಿನ",M:"ಒಂದು ತಿಂಗಳು",MM:"%d ತಿಂಗಳು",y:"ಒಂದು ವರ್ಷ",yy:"%d ವರ್ಷ"},preparse:function(e){return e.replace(/[೧೨೩೪೫೬೭೮೯೦]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/ರಾತ್ರಿ|ಬೆಳಿಗ್ಗೆ|ಮಧ್ಯಾಹ್ನ|ಸಂಜೆ/,meridiemHour:function(e,t){return 12===e&&(e=0),"ರಾತ್ರಿ"===t?e<4?e:e+12:"ಬೆಳಿಗ್ಗೆ"===t?e:"ಮಧ್ಯಾಹ್ನ"===t?e>=10?e:e+12:"ಸಂಜೆ"===t?e+12:void 0},meridiem:function(e,t,n){return e<4?"ರಾತ್ರಿ":e<10?"ಬೆಳಿಗ್ಗೆ":e<17?"ಮಧ್ಯಾಹ್ನ":e<20?"ಸಂಜೆ":"ರಾತ್ರಿ"},dayOfMonthOrdinalParse:/\d{1,2}(ನೇ)/,ordinal:function(e){return e+"ನೇ"},week:{dow:0,doy:6}})})},gEU3:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("mi",{months:"Kohi-tāte_Hui-tanguru_Poutū-te-rangi_Paenga-whāwhā_Haratua_Pipiri_Hōngoingoi_Here-turi-kōkā_Mahuru_Whiringa-ā-nuku_Whiringa-ā-rangi_Hakihea".split("_"),monthsShort:"Kohi_Hui_Pou_Pae_Hara_Pipi_Hōngoi_Here_Mahu_Whi-nu_Whi-ra_Haki".split("_"),monthsRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsStrictRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsShortRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsShortStrictRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,2}/i,weekdays:"Rātapu_Mane_Tūrei_Wenerei_Tāite_Paraire_Hātarei".split("_"),weekdaysShort:"Ta_Ma_Tū_We_Tāi_Pa_Hā".split("_"),weekdaysMin:"Ta_Ma_Tū_We_Tāi_Pa_Hā".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [i] HH:mm",LLLL:"dddd, D MMMM YYYY [i] HH:mm"},calendar:{sameDay:"[i teie mahana, i] LT",nextDay:"[apopo i] LT",nextWeek:"dddd [i] LT",lastDay:"[inanahi i] LT",lastWeek:"dddd [whakamutunga i] LT",sameElse:"L"},relativeTime:{future:"i roto i %s",past:"%s i mua",s:"te hēkona ruarua",ss:"%d hēkona",m:"he meneti",mm:"%d meneti",h:"te haora",hh:"%d haora",d:"he ra",dd:"%d ra",M:"he marama",MM:"%d marama",y:"he tau",yy:"%d tau"},dayOfMonthOrdinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}})})},gUgh:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("tet",{months:"Janeiru_Fevereiru_Marsu_Abril_Maiu_Juñu_Jullu_Agustu_Setembru_Outubru_Novembru_Dezembru".split("_"),monthsShort:"Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez".split("_"),weekdays:"Domingu_Segunda_Tersa_Kuarta_Kinta_Sesta_Sabadu".split("_"),weekdaysShort:"Dom_Seg_Ters_Kua_Kint_Sest_Sab".split("_"),weekdaysMin:"Do_Seg_Te_Ku_Ki_Ses_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Ohin iha] LT",nextDay:"[Aban iha] LT",nextWeek:"dddd [iha] LT",lastDay:"[Horiseik iha] LT",lastWeek:"dddd [semana kotuk] [iha] LT",sameElse:"L"},relativeTime:{future:"iha %s",past:"%s liuba",s:"minutu balun",ss:"minutu %d",m:"minutu ida",mm:"minutu %d",h:"oras ida",hh:"oras %d",d:"loron ida",dd:"loron %d",M:"fulan ida",MM:"fulan %d",y:"tinan ida",yy:"tinan %d"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10;return e+(1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")},week:{dow:1,doy:4}})})},gcLw:function(e,t,n){(function(e,t){if(void 0===e)throw new Error("Kimai requires jQuery");t.kimai={},t(function(){"use strict";t.kimai={init:function(e){void 0!==e&&(t.kimai.settings=t.extend({},t.kimai.defaults,e)),moment.locale(t.kimai.settings.locale),t("body").on("click","a.btn-trash",function(e){return confirm(t.kimai.settings.confirmDelete)}),t(".dropdown-toggle").dropdown(),t.kimai.settings.alertSuccessAutoHide>0&&setTimeout(function(){t("div.alert-success").alert("close")},t.kimai.settings.alertSuccessAutoHide),t('input[data-daterangepickerenable="on"]').each(function(e){var n=t(this).data("format"),i=t(this).data("separator"),a=(t.kimai.settings.today,{});a[t.kimai.settings.today]=[moment(),moment()],a[t.kimai.settings.yesterday]=[moment().subtract(1,"days"),moment().subtract(1,"days")],a[t.kimai.settings.thisWeek]=[moment().startOf("week"),moment().endOf("week")],a[t.kimai.settings.lastWeek]=[moment().subtract(1,"week").startOf("week"),moment().subtract(1,"week").endOf("week")],a[t.kimai.settings.thisMonth]=[moment().startOf("month"),moment().endOf("month")],a[t.kimai.settings.lastMonth]=[moment().subtract(1,"month").startOf("month"),moment().subtract(1,"month").endOf("month")],a[t.kimai.settings.thisYear]=[moment().startOf("year"),moment().endOf("year")],a[t.kimai.settings.lastYear]=[moment().subtract(1,"year").startOf("year"),moment().subtract(1,"year").endOf("year")],t(this).daterangepicker({showDropdowns:!0,autoUpdateInput:!1,autoApply:!1,linkedCalendars:!1,locale:{separator:i,format:n,firstDay:1,applyLabel:t.kimai.settings.apply,cancelLabel:t.kimai.settings.cancel,customRangeLabel:t.kimai.settings.customRange},ranges:a,alwaysShowCalendars:!0}),t(this).on("apply.daterangepicker",function(e,i){t(this).val(i.startDate.format(n)+" - "+i.endDate.format(n)),t(this).trigger("change")})}),t('input[data-datepickerenable="on"]').each(function(e){var n=t(this).data("format");t(this).daterangepicker({singleDatePicker:!0,showDropdowns:!0,autoUpdateInput:!1,locale:{format:n,firstDay:1,applyLabel:t.kimai.settings.apply,cancelLabel:t.kimai.settings.cancel,customRangeLabel:t.kimai.settings.customRange}}),t(this).on("apply.daterangepicker",function(e,i){t(this).val(i.startDate.format(n)),t(this).trigger("change")})}),t('input[data-datetimepicker="on"]').each(function(e){var n=t(this).data("format");t(this).daterangepicker({singleDatePicker:!0,timePicker:!0,timePicker24Hour:!0,showDropdowns:!0,autoUpdateInput:!1,locale:{format:n,firstDay:1,applyLabel:t.kimai.settings.apply,cancelLabel:t.kimai.settings.cancel,customRangeLabel:t.kimai.settings.customRange}}),t(this).on("apply.daterangepicker",function(e,i){t(this).val(i.startDate.format(n)),t(this).trigger("change")})}),t("select[data-related-select]").change(function(){var e=t(this).attr("data-api-url").replace("-s-",t(this).val()),n=t(this).attr("data-related-select");t.ajax({url:e,headers:{"X-AUTH-SESSION":!0,"Content-Type":"application/json"},method:"GET",dataType:"json",success:function(e){var i="#"+n,a=t(i),r=t(i+' option[value=""]');a.find("option").remove().end().find("optgroup").remove().end(),0!=r.length&&a.append('"),t.each(e,function(e,t){a.append('")}),a.trigger("change"),t(".selectpicker").selectpicker("refresh")}})}),t(".markdown-body :header").prepend(function(){t(this).prepend('')}),t(".markdown-body :header").hover(function(){t(this).find("a.anchor").show()},function(){t(this).find("a.anchor").hide()})},pauseRecord:function(e){t(e+" .pull-left i").hover(function(){var e=t(this).parents("a");e.attr("href",e.attr("href").replace("/stop","/pause")),t(this).removeClass("fa-stop-circle").addClass("fa-pause-circle").addClass("text-orange")},function(){var e=t(this).parents("a");e.attr("href",e.attr("href").replace("/pause","/stop")),t(this).removeClass("fa-pause-circle").removeClass("text-orange").addClass("fa-stop-circle")})}},t.kimai.defaults={locale:"en",alertSuccessAutoHide:5e3,confirmDelete:"Really delete?",today:"Today",yesterday:"Yesterday",apply:"Apply",cancel:"Cancel",thisWeek:"This week",lastWeek:"Last week",thisMonth:"This month",lastMonth:"Last month",thisYear:"This year",lastYear:"Last year",customRange:"Custom range"},t.kimai.settings={}})}).call(t,n("7t+N"),n("7t+N"))},hPuz:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("en-gb",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},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"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10;return e+(1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")},week:{dow:1,doy:4}})})},hng5:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("bm",{months:"Zanwuyekalo_Fewuruyekalo_Marisikalo_Awirilikalo_Mɛkalo_Zuwɛnkalo_Zuluyekalo_Utikalo_Sɛtanburukalo_ɔkutɔburukalo_Nowanburukalo_Desanburukalo".split("_"),monthsShort:"Zan_Few_Mar_Awi_Mɛ_Zuw_Zul_Uti_Sɛt_ɔku_Now_Des".split("_"),weekdays:"Kari_Ntɛnɛn_Tarata_Araba_Alamisa_Juma_Sibiri".split("_"),weekdaysShort:"Kar_Ntɛ_Tar_Ara_Ala_Jum_Sib".split("_"),weekdaysMin:"Ka_Nt_Ta_Ar_Al_Ju_Si".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"MMMM [tile] D [san] YYYY",LLL:"MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm",LLLL:"dddd MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm"},calendar:{sameDay:"[Bi lɛrɛ] LT",nextDay:"[Sini lɛrɛ] LT",nextWeek:"dddd [don lɛrɛ] LT",lastDay:"[Kunu lɛrɛ] LT",lastWeek:"dddd [tɛmɛnen lɛrɛ] LT",sameElse:"L"},relativeTime:{future:"%s kɔnɔ",past:"a bɛ %s bɔ",s:"sanga dama dama",ss:"sekondi %d",m:"miniti kelen",mm:"miniti %d",h:"lɛrɛ kelen",hh:"lɛrɛ %d",d:"tile kelen",dd:"tile %d",M:"kalo kelen",MM:"kalo %d",y:"san kelen",yy:"san %d"},week:{dow:1,doy:4}})})},iES9:function(e,t){},iNtv:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";function t(e,t,n,i){var a={s:["viensas secunds","'iensas secunds"],ss:[e+" secunds",e+" secunds"],m:["'n míut","'iens míut"],mm:[e+" míuts",e+" míuts"],h:["'n þora","'iensa þora"],hh:[e+" þoras",e+" þoras"],d:["'n ziua","'iensa ziua"],dd:[e+" ziuas",e+" ziuas"],M:["'n mes","'iens mes"],MM:[e+" mesen",e+" mesen"],y:["'n ar","'iens ar"],yy:[e+" ars",e+" ars"]};return i?a[n][0]:t?a[n][0]:a[n][1]}return e.defineLocale("tzl",{months:"Januar_Fevraglh_Març_Avrïu_Mai_Gün_Julia_Guscht_Setemvar_Listopäts_Noemvar_Zecemvar".split("_"),monthsShort:"Jan_Fev_Mar_Avr_Mai_Gün_Jul_Gus_Set_Lis_Noe_Zec".split("_"),weekdays:"Súladi_Lúneçi_Maitzi_Márcuri_Xhúadi_Viénerçi_Sáturi".split("_"),weekdaysShort:"Súl_Lún_Mai_Már_Xhú_Vié_Sát".split("_"),weekdaysMin:"Sú_Lú_Ma_Má_Xh_Vi_Sá".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"D. MMMM [dallas] YYYY",LLL:"D. MMMM [dallas] YYYY HH.mm",LLLL:"dddd, [li] D. MMMM [dallas] YYYY HH.mm"},meridiemParse:/d\'o|d\'a/i,isPM:function(e){return"d'o"===e.toLowerCase()},meridiem:function(e,t,n){return e>11?n?"d'o":"D'O":n?"d'a":"D'A"},calendar:{sameDay:"[oxhi à] LT",nextDay:"[demà à] LT",nextWeek:"dddd [à] LT",lastDay:"[ieiri à] LT",lastWeek:"[sür el] dddd [lasteu à] LT",sameElse:"L"},relativeTime:{future:"osprei %s",past:"ja%s",s:t,ss:t,m:t,mm:t,h:t,hh:t,d:t,dd:t,M:t,MM:t,y:t,yy:t},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})},"j+vx":function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={0:"-ші",1:"-ші",2:"-ші",3:"-ші",4:"-ші",5:"-ші",6:"-шы",7:"-ші",8:"-ші",9:"-шы",10:"-шы",20:"-шы",30:"-шы",40:"-шы",50:"-ші",60:"-шы",70:"-ші",80:"-ші",90:"-шы",100:"-ші"};return e.defineLocale("kk",{months:"қаңтар_ақпан_наурыз_сәуір_мамыр_маусым_шілде_тамыз_қыркүйек_қазан_қараша_желтоқсан".split("_"),monthsShort:"қаң_ақп_нау_сәу_мам_мау_шіл_там_қыр_қаз_қар_жел".split("_"),weekdays:"жексенбі_дүйсенбі_сейсенбі_сәрсенбі_бейсенбі_жұма_сенбі".split("_"),weekdaysShort:"жек_дүй_сей_сәр_бей_жұм_сен".split("_"),weekdaysMin:"жк_дй_сй_ср_бй_жм_сн".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Бүгін сағат] LT",nextDay:"[Ертең сағат] LT",nextWeek:"dddd [сағат] LT",lastDay:"[Кеше сағат] LT",lastWeek:"[Өткен аптаның] dddd [сағат] LT",sameElse:"L"},relativeTime:{future:"%s ішінде",past:"%s бұрын",s:"бірнеше секунд",ss:"%d секунд",m:"бір минут",mm:"%d минут",h:"бір сағат",hh:"%d сағат",d:"бір күн",dd:"%d күн",M:"бір ай",MM:"%d ай",y:"бір жыл",yy:"%d жыл"},dayOfMonthOrdinalParse:/\d{1,2}-(ші|шы)/,ordinal:function(e){var n=e%10,i=e>=100?100:null;return e+(t[e]||t[n]||t[i])},week:{dow:1,doy:7}})})},j8cJ:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("ar-kw",{months:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),monthsShort:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),weekdays:"الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[اليوم على الساعة] LT",nextDay:"[غدا على الساعة] LT",nextWeek:"dddd [على الساعة] LT",lastDay:"[أمس على الساعة] LT",lastWeek:"dddd [على الساعة] LT",sameElse:"L"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",ss:"%d ثانية",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"},week:{dow:0,doy:12}})})},jf49:function(e,t,n){(function(e){/*! +!function(e){function t(e,t,a){var r=e[0],o=/er/.test(a)?f:/bl/.test(a)?m:c,s=a==_?{checked:r[c],disabled:r[m],indeterminate:"true"==e.attr(f)||"false"==e.attr(p)}:r[o];if(/^(ch|di|in)/.test(a)&&!s)n(e,o);else if(/^(un|en|de)/.test(a)&&s)i(e,o);else if(a==_)for(var d in s)s[d]?n(e,d,!0):i(e,d,!0);else t&&"toggle"!=a||(t||e[b]("ifClicked"),s?r[g]!==u&&i(e,o):n(e,o))}function n(t,n,a){var _=t[0],y=t.parent(),b=n==c,L=n==f,k=n==m,w=L?p:b?h:"enabled",T=r(t,w+o(_[g])),x=r(t,n+o(_[g]));if(!0!==_[n]){if(!a&&n==c&&_[g]==u&&_.name){var Y=t.closest("form"),S='input[name="'+_.name+'"]';S=Y.length?Y.find(S):e(S),S.each(function(){this!==_&&e(this).data(d)&&i(e(this),n)})}L?(_[n]=!0,_[c]&&i(t,c,"force")):(a||(_[n]=!0),b&&_[f]&&i(t,f,!1)),s(t,b,n,a)}_[m]&&r(t,D,!0)&&y.find("."+l).css(D,"default"),y[v](x||r(t,n)||""),y.attr("role")&&!L&&y.attr("aria-"+(k?m:c),"true"),y[M](T||r(t,w)||"")}function i(e,t,n){var i=e[0],a=e.parent(),d=t==c,u=t==f,_=t==m,y=u?p:d?h:"enabled",b=r(e,y+o(i[g])),L=r(e,t+o(i[g]));!1!==i[t]&&(!u&&n&&"force"!=n||(i[t]=!1),s(e,d,y,n)),!i[m]&&r(e,D,!0)&&a.find("."+l).css(D,"pointer"),a[M](L||r(e,t)||""),a.attr("role")&&!u&&a.attr("aria-"+(_?m:c),"false"),a[v](b||r(e,y)||"")}function a(t,n){t.data(d)&&(t.parent().html(t.attr("style",t.data(d).s||"")),n&&t[b](n),t.off(".i").unwrap(),e(L+'[for="'+t[0].id+'"]').add(t.closest(L)).off(".i"))}function r(e,t,n){if(e.data(d))return e.data(d).o[t+(n?"":"Class")]}function o(e){return e.charAt(0).toUpperCase()+e.slice(1)}function s(e,t,n,i){i||(t&&e[b]("ifToggled"),e[b]("ifChanged")[b]("if"+o(n)))}var d="iCheck",l=d+"-helper",u="radio",c="checked",h="un"+c,m="disabled",p="determinate",f="in"+p,_="update",g="type",y="touchbegin.i touchend.i",v="addClass",M="removeClass",b="trigger",L="label",D="cursor",k=/ipad|iphone|ipod|android|blackberry|windows phone|opera mini|silk/i.test(navigator.userAgent);e.fn[d]=function(r,o){var s='input[type="checkbox"], input[type="'+u+'"]',h=e(),p=function(t){t.each(function(){var t=e(this);h=t.is(s)?h.add(t):h.add(t.find(s))})};if(/^(check|uncheck|toggle|indeterminate|determinate|disable|enable|update|destroy)$/i.test(r))return r=r.toLowerCase(),p(this),h.each(function(){var n=e(this);"destroy"==r?a(n,"ifDestroyed"):t(n,!0,r),e.isFunction(o)&&o()});if("object"!=typeof r&&r)return this;var D=e.extend({checkedClass:c,disabledClass:m,indeterminateClass:f,labelHover:!0},r),w=D.handle,T=D.hoverClass||"hover",x=D.focusClass||"focus",Y=D.activeClass||"active",S=!!D.labelHover,H=D.labelHoverClass||"hover",C=0|(""+D.increaseArea).replace("%","");return"checkbox"!=w&&w!=u||(s='input[type="'+w+'"]'),C<-50&&(C=-50),p(this),h.each(function(){var r=e(this);a(r);var o,s=this,h=s.id,p=-C+"%",f=100+2*C+"%",w={position:"absolute",top:p,left:p,display:"block",width:f,height:f,margin:0,padding:0,background:"#fff",border:0,opacity:0},E=k?{position:"absolute",visibility:"hidden"}:C?w:{position:"absolute",opacity:0},P="checkbox"==s[g]?D.checkboxClass||"icheckbox":D.radioClass||"i"+u,j=e(L+'[for="'+h+'"]').add(r.closest(L)),O=!!D.aria,A=d+"-"+Math.random().toString(36).substr(2,6),I='")[b]("ifCreated").parent().append(D.insert),o=e('').css(w).appendTo(I),r.data(d,{o:D,s:r.attr("style")}).css(E),!!D.inheritClass&&I[v](s.className||""),!!D.inheritID&&h&&I.attr("id",d+"-"+h),"static"==I.css("position")&&I.css("position","relative"),t(r,!0,_),j.length&&j.on("click.i mouseover.i mouseout.i "+y,function(n){var i=n[g],a=e(this);if(!s[m]){if("click"==i){if(e(n.target).is("a"))return;t(r,!1,!0)}else S&&(/ut|nd/.test(i)?(I[M](T),a[M](H)):(I[v](T),a[v](H)));if(!k)return!1;n.stopPropagation()}}),r.on("click.i focus.i blur.i keyup.i keydown.i keypress.i",function(e){var t=e[g],a=e.keyCode;return"click"!=t&&("keydown"==t&&32==a?(s[g]==u&&s[c]||(s[c]?i(r,c):n(r,c)),!1):void("keyup"==t&&s[g]==u?!s[c]&&n(r,c):/us|ur/.test(t)&&I["blur"==t?M:v](x)))}),o.on("click mousedown mouseup mouseover mouseout "+y,function(e){var n=e[g],i=/wn|up/.test(n)?Y:T;if(!s[m]){if("click"==n?t(r,!1,!0):(/wn|er|in/.test(n)?I[v](i):I[M](i+" "+Y),j.length&&S&&i==T&&j[/ut|nd/.test(n)?M:v](H)),!k)return!1;e.stopPropagation()}})})}}(e||window.Zepto)}).call(t,n("7t+N"))},aM0x:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={1:"১",2:"২",3:"৩",4:"৪",5:"৫",6:"৬",7:"৭",8:"৮",9:"৯",0:"০"},n={"১":"1","২":"2","৩":"3","৪":"4","৫":"5","৬":"6","৭":"7","৮":"8","৯":"9","০":"0"};return e.defineLocale("bn",{months:"জানুয়ারী_ফেব্রুয়ারি_মার্চ_এপ্রিল_মে_জুন_জুলাই_আগস্ট_সেপ্টেম্বর_অক্টোবর_নভেম্বর_ডিসেম্বর".split("_"),monthsShort:"জানু_ফেব_মার্চ_এপ্র_মে_জুন_জুল_আগ_সেপ্ট_অক্টো_নভে_ডিসে".split("_"),weekdays:"রবিবার_সোমবার_মঙ্গলবার_বুধবার_বৃহস্পতিবার_শুক্রবার_শনিবার".split("_"),weekdaysShort:"রবি_সোম_মঙ্গল_বুধ_বৃহস্পতি_শুক্র_শনি".split("_"),weekdaysMin:"রবি_সোম_মঙ্গ_বুধ_বৃহঃ_শুক্র_শনি".split("_"),longDateFormat:{LT:"A h:mm সময়",LTS:"A h:mm:ss সময়",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm সময়",LLLL:"dddd, D MMMM YYYY, A h:mm সময়"},calendar:{sameDay:"[আজ] LT",nextDay:"[আগামীকাল] LT",nextWeek:"dddd, LT",lastDay:"[গতকাল] LT",lastWeek:"[গত] dddd, LT",sameElse:"L"},relativeTime:{future:"%s পরে",past:"%s আগে",s:"কয়েক সেকেন্ড",ss:"%d সেকেন্ড",m:"এক মিনিট",mm:"%d মিনিট",h:"এক ঘন্টা",hh:"%d ঘন্টা",d:"এক দিন",dd:"%d দিন",M:"এক মাস",MM:"%d মাস",y:"এক বছর",yy:"%d বছর"},preparse:function(e){return e.replace(/[১২৩৪৫৬৭৮৯০]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/রাত|সকাল|দুপুর|বিকাল|রাত/,meridiemHour:function(e,t){return 12===e&&(e=0),"রাত"===t&&e>=4||"দুপুর"===t&&e<5||"বিকাল"===t?e+12:e},meridiem:function(e,t,n){return e<4?"রাত":e<10?"সকাল":e<17?"দুপুর":e<20?"বিকাল":"রাত"},week:{dow:0,doy:6}})})},aqvp:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";function t(e,t,n){var i=e+" ";switch(n){case"ss":return i+=1===e?"sekunda":2===e||3===e||4===e?"sekunde":"sekundi";case"m":return t?"jedna minuta":"jedne minute";case"mm":return i+=1===e?"minuta":2===e||3===e||4===e?"minute":"minuta";case"h":return t?"jedan sat":"jednog sata";case"hh":return i+=1===e?"sat":2===e||3===e||4===e?"sata":"sati";case"dd":return i+=1===e?"dan":"dana";case"MM":return i+=1===e?"mjesec":2===e||3===e||4===e?"mjeseca":"mjeseci";case"yy":return i+=1===e?"godina":2===e||3===e||4===e?"godine":"godina"}}return e.defineLocale("bs",{months:"januar_februar_mart_april_maj_juni_juli_august_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj._jun._jul._aug._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sri._čet._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_če_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedjelju] [u] LT";case 3:return"[u] [srijedu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[jučer u] LT",lastWeek:function(){switch(this.day()){case 0:case 3:return"[prošlu] dddd [u] LT";case 6:return"[prošle] [subote] [u] LT";case 1:case 2:case 4:case 5:return"[prošli] dddd [u] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"prije %s",s:"par sekundi",ss:t,m:t,mm:t,h:t,hh:t,d:"dan",dd:t,M:"mjesec",MM:t,y:"godinu",yy:t},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})},bXQP:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("fr-ca",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),monthsParseExact:!0,weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"di_lu_ma_me_je_ve_sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Aujourd’hui à] LT",nextDay:"[Demain à] LT",nextWeek:"dddd [à] LT",lastDay:"[Hier à] LT",lastWeek:"dddd [dernier à] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",ss:"%d secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},dayOfMonthOrdinalParse:/\d{1,2}(er|e)/,ordinal:function(e,t){switch(t){default:case"M":case"Q":case"D":case"DDD":case"d":return e+(1===e?"er":"e");case"w":case"W":return e+(1===e?"re":"e")}}})})},c0dd:function(e,t,n){n("chxu"),n("nyna"),n("olwm"),n("GOEF"),n("vg91"),n("AixB"),n("1QPC"),n("gcLw"),n("nRX6"),n("SAIs"),n("EmE7"),n("mN5h")},c1x4:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={words:{ss:["секунда","секунде","секунди"],m:["један минут","једне минуте"],mm:["минут","минуте","минута"],h:["један сат","једног сата"],hh:["сат","сата","сати"],dd:["дан","дана","дана"],MM:["месец","месеца","месеци"],yy:["година","године","година"]},correctGrammaticalCase:function(e,t){return 1===e?t[0]:e>=2&&e<=4?t[1]:t[2]},translate:function(e,n,i){var a=t.words[i];return 1===i.length?n?a[0]:a[1]:e+" "+t.correctGrammaticalCase(e,a)}};return e.defineLocale("sr-cyrl",{months:"јануар_фебруар_март_април_мај_јун_јул_август_септембар_октобар_новембар_децембар".split("_"),monthsShort:"јан._феб._мар._апр._мај_јун_јул_авг._сеп._окт._нов._дец.".split("_"),monthsParseExact:!0,weekdays:"недеља_понедељак_уторак_среда_четвртак_петак_субота".split("_"),weekdaysShort:"нед._пон._уто._сре._чет._пет._суб.".split("_"),weekdaysMin:"не_по_ут_ср_че_пе_су".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[данас у] LT",nextDay:"[сутра у] LT",nextWeek:function(){switch(this.day()){case 0:return"[у] [недељу] [у] LT";case 3:return"[у] [среду] [у] LT";case 6:return"[у] [суботу] [у] LT";case 1:case 2:case 4:case 5:return"[у] dddd [у] LT"}},lastDay:"[јуче у] LT",lastWeek:function(){return["[прошле] [недеље] [у] LT","[прошлог] [понедељка] [у] LT","[прошлог] [уторка] [у] LT","[прошле] [среде] [у] LT","[прошлог] [четвртка] [у] LT","[прошлог] [петка] [у] LT","[прошле] [суботе] [у] LT"][this.day()]},sameElse:"L"},relativeTime:{future:"за %s",past:"пре %s",s:"неколико секунди",ss:t.translate,m:t.translate,mm:t.translate,h:t.translate,hh:t.translate,d:"дан",dd:t.translate,M:"месец",MM:t.translate,y:"годину",yy:t.translate},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})},chxu:function(e,t,n){(function(e){var t=n("7t+N");e.$=e.jQuery=t,n("z1kw"),n("jf49"),n("DBzq"),n("tNVI");var i=n("PJh5");e.moment=i,n("PL/P"),n("WFwX"),n("R6CI"),n("iES9"),n("FDd9"),e.$.AdminLTE={},e.$.AdminLTE.options={},n("Dehm"),n("l77c"),n("a7jZ"),n("3EEf")}).call(t,n("DuR2"))},dURR:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("ar-ma",{months:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),monthsShort:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),weekdays:"الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[اليوم على الساعة] LT",nextDay:"[غدا على الساعة] LT",nextWeek:"dddd [على الساعة] LT",lastDay:"[أمس على الساعة] LT",lastWeek:"dddd [على الساعة] LT",sameElse:"L"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",ss:"%d ثانية",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"},week:{dow:6,doy:12}})})},dyB6:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("en-nz",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},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"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10;return e+(1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")},week:{dow:1,doy:4}})})},"e/KL":function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("x-pseudo",{months:"J~áñúá~rý_F~ébrú~árý_~Márc~h_Áp~ríl_~Máý_~Júñé~_Júl~ý_Áú~gúst~_Sép~témb~ér_Ó~ctób~ér_Ñ~óvém~bér_~Décé~mbér".split("_"),monthsShort:"J~áñ_~Féb_~Már_~Ápr_~Máý_~Júñ_~Júl_~Áúg_~Sép_~Óct_~Ñóv_~Déc".split("_"),monthsParseExact:!0,weekdays:"S~úñdá~ý_Mó~ñdáý~_Túé~sdáý~_Wéd~ñésd~áý_T~húrs~dáý_~Fríd~áý_S~átúr~dáý".split("_"),weekdaysShort:"S~úñ_~Móñ_~Túé_~Wéd_~Thú_~Frí_~Sát".split("_"),weekdaysMin:"S~ú_Mó~_Tú_~Wé_T~h_Fr~_Sá".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[T~ódá~ý át] LT",nextDay:"[T~ómó~rró~w át] LT",nextWeek:"dddd [át] LT",lastDay:"[Ý~ést~érdá~ý át] LT",lastWeek:"[L~ást] dddd [át] LT",sameElse:"L"},relativeTime:{future:"í~ñ %s",past:"%s á~gó",s:"á ~féw ~sécó~ñds",ss:"%d s~écóñ~ds",m:"á ~míñ~úté",mm:"%d m~íñú~tés",h:"á~ñ hó~úr",hh:"%d h~óúrs",d:"á ~dáý",dd:"%d d~áýs",M:"á ~móñ~th",MM:"%d m~óñt~hs",y:"á ~ýéár",yy:"%d ý~éárs"},dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10;return e+(1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")},week:{dow:1,doy:4}})})},"eBB/":function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("ko",{months:"1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),monthsShort:"1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),weekdays:"일요일_월요일_화요일_수요일_목요일_금요일_토요일".split("_"),weekdaysShort:"일_월_화_수_목_금_토".split("_"),weekdaysMin:"일_월_화_수_목_금_토".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"YYYY.MM.DD.",LL:"YYYY년 MMMM D일",LLL:"YYYY년 MMMM D일 A h:mm",LLLL:"YYYY년 MMMM D일 dddd A h:mm",l:"YYYY.MM.DD.",ll:"YYYY년 MMMM D일",lll:"YYYY년 MMMM D일 A h:mm",llll:"YYYY년 MMMM D일 dddd A h:mm"},calendar:{sameDay:"오늘 LT",nextDay:"내일 LT",nextWeek:"dddd LT",lastDay:"어제 LT",lastWeek:"지난주 dddd LT",sameElse:"L"},relativeTime:{future:"%s 후",past:"%s 전",s:"몇 초",ss:"%d초",m:"1분",mm:"%d분",h:"한 시간",hh:"%d시간",d:"하루",dd:"%d일",M:"한 달",MM:"%d달",y:"일 년",yy:"%d년"},dayOfMonthOrdinalParse:/\d{1,2}(일|월|주)/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"일";case"M":return e+"월";case"w":case"W":return e+"주";default:return e}},meridiemParse:/오전|오후/,isPM:function(e){return"오후"===e},meridiem:function(e,t,n){return e<12?"오전":"오후"}})})},eHwN:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={1:"-inci",5:"-inci",8:"-inci",70:"-inci",80:"-inci",2:"-nci",7:"-nci",20:"-nci",50:"-nci",3:"-üncü",4:"-üncü",100:"-üncü",6:"-ncı",9:"-uncu",10:"-uncu",30:"-uncu",60:"-ıncı",90:"-ıncı"};return e.defineLocale("az",{months:"yanvar_fevral_mart_aprel_may_iyun_iyul_avqust_sentyabr_oktyabr_noyabr_dekabr".split("_"),monthsShort:"yan_fev_mar_apr_may_iyn_iyl_avq_sen_okt_noy_dek".split("_"),weekdays:"Bazar_Bazar ertəsi_Çərşənbə axşamı_Çərşənbə_Cümə axşamı_Cümə_Şənbə".split("_"),weekdaysShort:"Baz_BzE_ÇAx_Çər_CAx_Cüm_Şən".split("_"),weekdaysMin:"Bz_BE_ÇA_Çə_CA_Cü_Şə".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[bugün saat] LT",nextDay:"[sabah saat] LT",nextWeek:"[gələn həftə] dddd [saat] LT",lastDay:"[dünən] LT",lastWeek:"[keçən həftə] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s əvvəl",s:"birneçə saniyə",ss:"%d saniyə",m:"bir dəqiqə",mm:"%d dəqiqə",h:"bir saat",hh:"%d saat",d:"bir gün",dd:"%d gün",M:"bir ay",MM:"%d ay",y:"bir il",yy:"%d il"},meridiemParse:/gecə|səhər|gündüz|axşam/,isPM:function(e){return/^(gündüz|axşam)$/.test(e)},meridiem:function(e,t,n){return e<4?"gecə":e<12?"səhər":e<17?"gündüz":"axşam"},dayOfMonthOrdinalParse:/\d{1,2}-(ıncı|inci|nci|üncü|ncı|uncu)/,ordinal:function(e){if(0===e)return e+"-ıncı";var n=e%10,i=e%100-n,a=e>=100?100:null;return e+(t[n]||t[i]||t[a])},week:{dow:1,doy:7}})})},f4W3:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={words:{ss:["sekunda","sekunde","sekundi"],m:["jedan minut","jedne minute"],mm:["minut","minute","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mesec","meseca","meseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(e,t){return 1===e?t[0]:e>=2&&e<=4?t[1]:t[2]},translate:function(e,n,i){var a=t.words[i];return 1===i.length?n?a[0]:a[1]:e+" "+t.correctGrammaticalCase(e,a)}};return e.defineLocale("sr",{months:"januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedelja_ponedeljak_utorak_sreda_četvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sre._čet._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_če_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedelju] [u] LT";case 3:return"[u] [sredu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[juče u] LT",lastWeek:function(){return["[prošle] [nedelje] [u] LT","[prošlog] [ponedeljka] [u] LT","[prošlog] [utorka] [u] LT","[prošle] [srede] [u] LT","[prošlog] [četvrtka] [u] LT","[prošlog] [petka] [u] LT","[prošle] [subote] [u] LT"][this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"pre %s",s:"nekoliko sekundi",ss:t.translate,m:t.translate,mm:t.translate,h:t.translate,hh:t.translate,d:"dan",dd:t.translate,M:"mesec",MM:t.translate,y:"godinu",yy:t.translate},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})},fW1y:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t=["جنوري","فيبروري","مارچ","اپريل","مئي","جون","جولاءِ","آگسٽ","سيپٽمبر","آڪٽوبر","نومبر","ڊسمبر"],n=["آچر","سومر","اڱارو","اربع","خميس","جمع","ڇنڇر"];return e.defineLocale("sd",{months:t,monthsShort:t,weekdays:n,weekdaysShort:n,weekdaysMin:n,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd، D MMMM YYYY HH:mm"},meridiemParse:/صبح|شام/,isPM:function(e){return"شام"===e},meridiem:function(e,t,n){return e<12?"صبح":"شام"},calendar:{sameDay:"[اڄ] LT",nextDay:"[سڀاڻي] LT",nextWeek:"dddd [اڳين هفتي تي] LT",lastDay:"[ڪالهه] LT",lastWeek:"[گزريل هفتي] dddd [تي] LT",sameElse:"L"},relativeTime:{future:"%s پوء",past:"%s اڳ",s:"چند سيڪنڊ",ss:"%d سيڪنڊ",m:"هڪ منٽ",mm:"%d منٽ",h:"هڪ ڪلاڪ",hh:"%d ڪلاڪ",d:"هڪ ڏينهن",dd:"%d ڏينهن",M:"هڪ مهينو",MM:"%d مهينا",y:"هڪ سال",yy:"%d سال"},preparse:function(e){return e.replace(/،/g,",")},postformat:function(e){return e.replace(/,/g,"،")},week:{dow:1,doy:4}})})},g7KF:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t="jan._feb._mrt._apr._mai_jun._jul._aug._sep._okt._nov._des.".split("_"),n="jan_feb_mrt_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_");return e.defineLocale("fy",{months:"jannewaris_febrewaris_maart_april_maaie_juny_july_augustus_septimber_oktober_novimber_desimber".split("_"),monthsShort:function(e,i){return e?/-MMM-/.test(i)?n[e.month()]:t[e.month()]:t},monthsParseExact:!0,weekdays:"snein_moandei_tiisdei_woansdei_tongersdei_freed_sneon".split("_"),weekdaysShort:"si._mo._ti._wo._to._fr._so.".split("_"),weekdaysMin:"Si_Mo_Ti_Wo_To_Fr_So".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[hjoed om] LT",nextDay:"[moarn om] LT",nextWeek:"dddd [om] LT",lastDay:"[juster om] LT",lastWeek:"[ôfrûne] dddd [om] LT",sameElse:"L"},relativeTime:{future:"oer %s",past:"%s lyn",s:"in pear sekonden",ss:"%d sekonden",m:"ien minút",mm:"%d minuten",h:"ien oere",hh:"%d oeren",d:"ien dei",dd:"%d dagen",M:"ien moanne",MM:"%d moannen",y:"ien jier",yy:"%d jierren"},dayOfMonthOrdinalParse:/\d{1,2}(ste|de)/,ordinal:function(e){return e+(1===e||8===e||e>=20?"ste":"de")},week:{dow:1,doy:4}})})},gEQe:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={1:"೧",2:"೨",3:"೩",4:"೪",5:"೫",6:"೬",7:"೭",8:"೮",9:"೯",0:"೦"},n={"೧":"1","೨":"2","೩":"3","೪":"4","೫":"5","೬":"6","೭":"7","೮":"8","೯":"9","೦":"0"};return e.defineLocale("kn",{months:"ಜನವರಿ_ಫೆಬ್ರವರಿ_ಮಾರ್ಚ್_ಏಪ್ರಿಲ್_ಮೇ_ಜೂನ್_ಜುಲೈ_ಆಗಸ್ಟ್_ಸೆಪ್ಟೆಂಬರ್_ಅಕ್ಟೋಬರ್_ನವೆಂಬರ್_ಡಿಸೆಂಬರ್".split("_"),monthsShort:"ಜನ_ಫೆಬ್ರ_ಮಾರ್ಚ್_ಏಪ್ರಿಲ್_ಮೇ_ಜೂನ್_ಜುಲೈ_ಆಗಸ್ಟ್_ಸೆಪ್ಟೆಂ_ಅಕ್ಟೋ_ನವೆಂ_ಡಿಸೆಂ".split("_"),monthsParseExact:!0,weekdays:"ಭಾನುವಾರ_ಸೋಮವಾರ_ಮಂಗಳವಾರ_ಬುಧವಾರ_ಗುರುವಾರ_ಶುಕ್ರವಾರ_ಶನಿವಾರ".split("_"),weekdaysShort:"ಭಾನು_ಸೋಮ_ಮಂಗಳ_ಬುಧ_ಗುರು_ಶುಕ್ರ_ಶನಿ".split("_"),weekdaysMin:"ಭಾ_ಸೋ_ಮಂ_ಬು_ಗು_ಶು_ಶ".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm",LLLL:"dddd, D MMMM YYYY, A h:mm"},calendar:{sameDay:"[ಇಂದು] LT",nextDay:"[ನಾಳೆ] LT",nextWeek:"dddd, LT",lastDay:"[ನಿನ್ನೆ] LT",lastWeek:"[ಕೊನೆಯ] dddd, LT",sameElse:"L"},relativeTime:{future:"%s ನಂತರ",past:"%s ಹಿಂದೆ",s:"ಕೆಲವು ಕ್ಷಣಗಳು",ss:"%d ಸೆಕೆಂಡುಗಳು",m:"ಒಂದು ನಿಮಿಷ",mm:"%d ನಿಮಿಷ",h:"ಒಂದು ಗಂಟೆ",hh:"%d ಗಂಟೆ",d:"ಒಂದು ದಿನ",dd:"%d ದಿನ",M:"ಒಂದು ತಿಂಗಳು",MM:"%d ತಿಂಗಳು",y:"ಒಂದು ವರ್ಷ",yy:"%d ವರ್ಷ"},preparse:function(e){return e.replace(/[೧೨೩೪೫೬೭೮೯೦]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/ರಾತ್ರಿ|ಬೆಳಿಗ್ಗೆ|ಮಧ್ಯಾಹ್ನ|ಸಂಜೆ/,meridiemHour:function(e,t){return 12===e&&(e=0),"ರಾತ್ರಿ"===t?e<4?e:e+12:"ಬೆಳಿಗ್ಗೆ"===t?e:"ಮಧ್ಯಾಹ್ನ"===t?e>=10?e:e+12:"ಸಂಜೆ"===t?e+12:void 0},meridiem:function(e,t,n){return e<4?"ರಾತ್ರಿ":e<10?"ಬೆಳಿಗ್ಗೆ":e<17?"ಮಧ್ಯಾಹ್ನ":e<20?"ಸಂಜೆ":"ರಾತ್ರಿ"},dayOfMonthOrdinalParse:/\d{1,2}(ನೇ)/,ordinal:function(e){return e+"ನೇ"},week:{dow:0,doy:6}})})},gEU3:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("mi",{months:"Kohi-tāte_Hui-tanguru_Poutū-te-rangi_Paenga-whāwhā_Haratua_Pipiri_Hōngoingoi_Here-turi-kōkā_Mahuru_Whiringa-ā-nuku_Whiringa-ā-rangi_Hakihea".split("_"),monthsShort:"Kohi_Hui_Pou_Pae_Hara_Pipi_Hōngoi_Here_Mahu_Whi-nu_Whi-ra_Haki".split("_"),monthsRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsStrictRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsShortRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsShortStrictRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,2}/i,weekdays:"Rātapu_Mane_Tūrei_Wenerei_Tāite_Paraire_Hātarei".split("_"),weekdaysShort:"Ta_Ma_Tū_We_Tāi_Pa_Hā".split("_"),weekdaysMin:"Ta_Ma_Tū_We_Tāi_Pa_Hā".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [i] HH:mm",LLLL:"dddd, D MMMM YYYY [i] HH:mm"},calendar:{sameDay:"[i teie mahana, i] LT",nextDay:"[apopo i] LT",nextWeek:"dddd [i] LT",lastDay:"[inanahi i] LT",lastWeek:"dddd [whakamutunga i] LT",sameElse:"L"},relativeTime:{future:"i roto i %s",past:"%s i mua",s:"te hēkona ruarua",ss:"%d hēkona",m:"he meneti",mm:"%d meneti",h:"te haora",hh:"%d haora",d:"he ra",dd:"%d ra",M:"he marama",MM:"%d marama",y:"he tau",yy:"%d tau"},dayOfMonthOrdinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}})})},gUgh:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("tet",{months:"Janeiru_Fevereiru_Marsu_Abril_Maiu_Juñu_Jullu_Agustu_Setembru_Outubru_Novembru_Dezembru".split("_"),monthsShort:"Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez".split("_"),weekdays:"Domingu_Segunda_Tersa_Kuarta_Kinta_Sesta_Sabadu".split("_"),weekdaysShort:"Dom_Seg_Ters_Kua_Kint_Sest_Sab".split("_"),weekdaysMin:"Do_Seg_Te_Ku_Ki_Ses_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Ohin iha] LT",nextDay:"[Aban iha] LT",nextWeek:"dddd [iha] LT",lastDay:"[Horiseik iha] LT",lastWeek:"dddd [semana kotuk] [iha] LT",sameElse:"L"},relativeTime:{future:"iha %s",past:"%s liuba",s:"minutu balun",ss:"minutu %d",m:"minutu ida",mm:"minutu %d",h:"oras ida",hh:"oras %d",d:"loron ida",dd:"loron %d",M:"fulan ida",MM:"fulan %d",y:"tinan ida",yy:"tinan %d"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10;return e+(1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")},week:{dow:1,doy:4}})})},gcLw:function(e,t,n){(function(e,t){if(void 0===e)throw new Error("Kimai requires jQuery");if("undefined"==typeof moment)throw new Error("Kimai requires moment.js");t.kimai={},t(function(){"use strict";t.kimai={init:function(e){void 0!==e&&(t.kimai.settings=t.extend({},t.kimai.defaults,e)),moment.locale(t.kimai.settings.locale),t("body").on("click","a.btn-trash",function(e){return confirm(t.kimai.settings.confirmDelete)}),t(".dropdown-toggle").dropdown(),t.kimai.settings.alertSuccessAutoHide>0&&setTimeout(function(){t("div.alert-success").alert("close")},t.kimai.settings.alertSuccessAutoHide),t('input[data-daterangepickerenable="on"]').each(function(e){var n=t(this).data("format"),i=t(this).data("separator"),a=(t.kimai.settings.today,{});a[t.kimai.settings.today]=[moment(),moment()],a[t.kimai.settings.yesterday]=[moment().subtract(1,"days"),moment().subtract(1,"days")],a[t.kimai.settings.thisWeek]=[moment().startOf("week"),moment().endOf("week")],a[t.kimai.settings.lastWeek]=[moment().subtract(1,"week").startOf("week"),moment().subtract(1,"week").endOf("week")],a[t.kimai.settings.thisMonth]=[moment().startOf("month"),moment().endOf("month")],a[t.kimai.settings.lastMonth]=[moment().subtract(1,"month").startOf("month"),moment().subtract(1,"month").endOf("month")],a[t.kimai.settings.thisYear]=[moment().startOf("year"),moment().endOf("year")],a[t.kimai.settings.lastYear]=[moment().subtract(1,"year").startOf("year"),moment().subtract(1,"year").endOf("year")],t(this).daterangepicker({showDropdowns:!0,autoUpdateInput:!1,autoApply:!1,linkedCalendars:!1,locale:{separator:i,format:n,firstDay:1,applyLabel:t.kimai.settings.apply,cancelLabel:t.kimai.settings.cancel,customRangeLabel:t.kimai.settings.customRange},ranges:a,alwaysShowCalendars:!0}),t(this).on("apply.daterangepicker",function(e,i){t(this).val(i.startDate.format(n)+" - "+i.endDate.format(n)),t(this).trigger("change")})}),t('input[data-datepickerenable="on"]').each(function(e){var n=t(this).data("format");t(this).daterangepicker({singleDatePicker:!0,showDropdowns:!0,autoUpdateInput:!1,locale:{format:n,firstDay:1,applyLabel:t.kimai.settings.apply,cancelLabel:t.kimai.settings.cancel,customRangeLabel:t.kimai.settings.customRange}}),t(this).on("apply.daterangepicker",function(e,i){t(this).val(i.startDate.format(n)),t(this).trigger("change")})}),t('input[data-datetimepicker="on"]').each(function(e){var n=t(this).data("format");t(this).daterangepicker({singleDatePicker:!0,timePicker:!0,timePicker24Hour:!0,showDropdowns:!0,autoUpdateInput:!1,locale:{format:n,firstDay:1,applyLabel:t.kimai.settings.apply,cancelLabel:t.kimai.settings.cancel,customRangeLabel:t.kimai.settings.customRange}}),t(this).on("apply.daterangepicker",function(e,i){t(this).val(i.startDate.format(n)),t(this).trigger("change")})}),t("select[data-related-select]").change(function(){var e=t(this).attr("data-api-url").replace("-s-",t(this).val()),n=t(this).attr("data-related-select");t.ajax({url:e,headers:{"X-AUTH-SESSION":!0,"Content-Type":"application/json"},method:"GET",dataType:"json",success:function(e){var i="#"+n,a=t(i),r=t(i+' option[value=""]');a.find("option").remove().end().find("optgroup").remove().end(),0!=r.length&&a.append('"),t.each(e,function(e,t){a.append('")}),a.trigger("change"),t(".selectpicker").selectpicker("refresh")}})}),t(".markdown-body :header").prepend(function(){t(this).prepend('')}),t(".markdown-body :header").hover(function(){t(this).find("a.anchor").show()},function(){t(this).find("a.anchor").hide()})},pauseRecord:function(e){t(e+" .pull-left i").hover(function(){var e=t(this).parents("a");e.attr("href",e.attr("href").replace("/stop","/pause")),t(this).removeClass("fa-stop-circle").addClass("fa-pause-circle").addClass("text-orange")},function(){var e=t(this).parents("a");e.attr("href",e.attr("href").replace("/pause","/stop")),t(this).removeClass("fa-pause-circle").removeClass("text-orange").addClass("fa-stop-circle")})}},t.kimai.defaults={locale:"en",alertSuccessAutoHide:5e3,confirmDelete:"Really delete?",today:"Today",yesterday:"Yesterday",apply:"Apply",cancel:"Cancel",thisWeek:"This week",lastWeek:"Last week",thisMonth:"This month",lastMonth:"Last month",thisYear:"This year",lastYear:"Last year",customRange:"Custom range"},t.kimai.settings={}})}).call(t,n("7t+N"),n("7t+N"))},hPuz:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("en-gb",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},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"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10;return e+(1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")},week:{dow:1,doy:4}})})},hng5:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("bm",{months:"Zanwuyekalo_Fewuruyekalo_Marisikalo_Awirilikalo_Mɛkalo_Zuwɛnkalo_Zuluyekalo_Utikalo_Sɛtanburukalo_ɔkutɔburukalo_Nowanburukalo_Desanburukalo".split("_"),monthsShort:"Zan_Few_Mar_Awi_Mɛ_Zuw_Zul_Uti_Sɛt_ɔku_Now_Des".split("_"),weekdays:"Kari_Ntɛnɛn_Tarata_Araba_Alamisa_Juma_Sibiri".split("_"),weekdaysShort:"Kar_Ntɛ_Tar_Ara_Ala_Jum_Sib".split("_"),weekdaysMin:"Ka_Nt_Ta_Ar_Al_Ju_Si".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"MMMM [tile] D [san] YYYY",LLL:"MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm",LLLL:"dddd MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm"},calendar:{sameDay:"[Bi lɛrɛ] LT",nextDay:"[Sini lɛrɛ] LT",nextWeek:"dddd [don lɛrɛ] LT",lastDay:"[Kunu lɛrɛ] LT",lastWeek:"dddd [tɛmɛnen lɛrɛ] LT",sameElse:"L"},relativeTime:{future:"%s kɔnɔ",past:"a bɛ %s bɔ",s:"sanga dama dama",ss:"sekondi %d",m:"miniti kelen",mm:"miniti %d",h:"lɛrɛ kelen",hh:"lɛrɛ %d",d:"tile kelen",dd:"tile %d",M:"kalo kelen",MM:"kalo %d",y:"san kelen",yy:"san %d"},week:{dow:1,doy:4}})})},iES9:function(e,t){},iNtv:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";function t(e,t,n,i){var a={s:["viensas secunds","'iensas secunds"],ss:[e+" secunds",e+" secunds"],m:["'n míut","'iens míut"],mm:[e+" míuts",e+" míuts"],h:["'n þora","'iensa þora"],hh:[e+" þoras",e+" þoras"],d:["'n ziua","'iensa ziua"],dd:[e+" ziuas",e+" ziuas"],M:["'n mes","'iens mes"],MM:[e+" mesen",e+" mesen"],y:["'n ar","'iens ar"],yy:[e+" ars",e+" ars"]};return i?a[n][0]:t?a[n][0]:a[n][1]}return e.defineLocale("tzl",{months:"Januar_Fevraglh_Març_Avrïu_Mai_Gün_Julia_Guscht_Setemvar_Listopäts_Noemvar_Zecemvar".split("_"),monthsShort:"Jan_Fev_Mar_Avr_Mai_Gün_Jul_Gus_Set_Lis_Noe_Zec".split("_"),weekdays:"Súladi_Lúneçi_Maitzi_Márcuri_Xhúadi_Viénerçi_Sáturi".split("_"),weekdaysShort:"Súl_Lún_Mai_Már_Xhú_Vié_Sát".split("_"),weekdaysMin:"Sú_Lú_Ma_Má_Xh_Vi_Sá".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"D. MMMM [dallas] YYYY",LLL:"D. MMMM [dallas] YYYY HH.mm",LLLL:"dddd, [li] D. MMMM [dallas] YYYY HH.mm"},meridiemParse:/d\'o|d\'a/i,isPM:function(e){return"d'o"===e.toLowerCase()},meridiem:function(e,t,n){return e>11?n?"d'o":"D'O":n?"d'a":"D'A"},calendar:{sameDay:"[oxhi à] LT",nextDay:"[demà à] LT",nextWeek:"dddd [à] LT",lastDay:"[ieiri à] LT",lastWeek:"[sür el] dddd [lasteu à] LT",sameElse:"L"},relativeTime:{future:"osprei %s",past:"ja%s",s:t,ss:t,m:t,mm:t,h:t,hh:t,d:t,dd:t,M:t,MM:t,y:t,yy:t},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})},"j+vx":function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={0:"-ші",1:"-ші",2:"-ші",3:"-ші",4:"-ші",5:"-ші",6:"-шы",7:"-ші",8:"-ші",9:"-шы",10:"-шы",20:"-шы",30:"-шы",40:"-шы",50:"-ші",60:"-шы",70:"-ші",80:"-ші",90:"-шы",100:"-ші"};return e.defineLocale("kk",{months:"қаңтар_ақпан_наурыз_сәуір_мамыр_маусым_шілде_тамыз_қыркүйек_қазан_қараша_желтоқсан".split("_"),monthsShort:"қаң_ақп_нау_сәу_мам_мау_шіл_там_қыр_қаз_қар_жел".split("_"),weekdays:"жексенбі_дүйсенбі_сейсенбі_сәрсенбі_бейсенбі_жұма_сенбі".split("_"),weekdaysShort:"жек_дүй_сей_сәр_бей_жұм_сен".split("_"),weekdaysMin:"жк_дй_сй_ср_бй_жм_сн".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Бүгін сағат] LT",nextDay:"[Ертең сағат] LT",nextWeek:"dddd [сағат] LT",lastDay:"[Кеше сағат] LT",lastWeek:"[Өткен аптаның] dddd [сағат] LT",sameElse:"L"},relativeTime:{future:"%s ішінде",past:"%s бұрын",s:"бірнеше секунд",ss:"%d секунд",m:"бір минут",mm:"%d минут",h:"бір сағат",hh:"%d сағат",d:"бір күн",dd:"%d күн",M:"бір ай",MM:"%d ай",y:"бір жыл",yy:"%d жыл"},dayOfMonthOrdinalParse:/\d{1,2}-(ші|шы)/,ordinal:function(e){var n=e%10,i=e>=100?100:null;return e+(t[e]||t[n]||t[i])},week:{dow:1,doy:7}})})},j8cJ:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("ar-kw",{months:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),monthsShort:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),weekdays:"الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[اليوم على الساعة] LT",nextDay:"[غدا على الساعة] LT",nextWeek:"dddd [على الساعة] LT",lastDay:"[أمس على الساعة] LT",lastWeek:"dddd [على الساعة] LT",sameElse:"L"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",ss:"%d ثانية",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"},week:{dow:0,doy:12}})})},jf49:function(e,t,n){(function(e){/*! * Bootstrap v3.3.7 (http://getbootstrap.com) * Copyright 2011-2016 Twitter, Inc. * Licensed under the MIT license diff --git a/public/build/manifest.json b/public/build/manifest.json index 76afe99a..0f66409d 100644 --- a/public/build/manifest.json +++ b/public/build/manifest.json @@ -1,5 +1,5 @@ { - "build/app.js": "/build/app.js?bf44c766db95ef7d121c", + "build/app.js": "/build/app.js?19fe460d7c7c857b408b", "build/app.css": "/build/app.css?caadc4d6101e9889a4fa28e77f1d9362", "build/images/blue@2x.png": "/build/images/blue@2x.png?2694acfd", "build/images/blue.png": "/build/images/blue.png?96f8a905", diff --git a/src/Command/BashExecutor.php b/src/Command/BashExecutor.php new file mode 100644 index 00000000..6f699b31 --- /dev/null +++ b/src/Command/BashExecutor.php @@ -0,0 +1,43 @@ +rootDir = realpath($projectDirectory); + } + + /** + * @param string $command + * @return BashResult + */ + public function execute(string $command) + { + $exitCode = 0; + + $command = rtrim($this->rootDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . ltrim($command, DIRECTORY_SEPARATOR); + + ob_start(); + passthru($command, $exitCode); + $result = ob_get_clean(); + + return new BashResult($exitCode, $result); + } +} diff --git a/src/Command/BashResult.php b/src/Command/BashResult.php new file mode 100644 index 00000000..d8f1dee3 --- /dev/null +++ b/src/Command/BashResult.php @@ -0,0 +1,70 @@ +exitCode = $exitCode; + $this->result = $result; + } + + /** + * @return string + */ + public function getExitCode(): string + { + return $this->exitCode; + } + + /** + * @param string $exitCode + * @return BashResult + */ + public function setExitCode(string $exitCode) + { + $this->exitCode = $exitCode; + + return $this; + } + + /** + * @return string + */ + public function getResult(): string + { + return $this->result; + } + + /** + * @param string $result + * @return BashResult + */ + public function setResult(string $result) + { + $this->result = $result; + + return $this; + } +} diff --git a/src/Command/ConvertTimezoneCommand.php b/src/Command/ConvertTimezoneCommand.php new file mode 100644 index 00000000..a23e7efe --- /dev/null +++ b/src/Command/ConvertTimezoneCommand.php @@ -0,0 +1,147 @@ +repository = $repository; + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('kimai:convert-timezone') + ->setDescription('Convert timesheet dates between timezones, only made for updates from 0.7 to 0.8') + ->setHelp('This command should only be required if you imported data from Kimai v1') + ->addOption('first-id', 'f', InputArgument::OPTIONAL, 'The database ID which should be converted first') + ->addOption('last-id', 'l', InputArgument::OPTIONAL, 'The database ID which should be converted last') + ; + } + + /** + * @param null|string $start + * @param null|string $end + * @return Timesheet[] + */ + protected function getTimesheets($start = null, $end = null) + { + $qb = $this->repository->createQueryBuilder('t'); + + $qb->select('t'); + if (!empty($start)) { + $qb->andWhere($qb->expr()->gte('t.id', $start)); + } + if (!empty($end)) { + $qb->andWhere($qb->expr()->lte('t.id', $end)); + } + + return $qb->getQuery()->getResult(); + } + + /** + * @param Timesheet $timesheet + * @param \DateTimeZone $timeZone + * @return Timesheet + */ + protected function convertTimesheet(Timesheet $timesheet, \DateTimeZone $timeZone): Timesheet + { + $oldTimezone = $timesheet->getTimezone(); + + if (null !== $timesheet->getBegin()) { + $beginDate = clone $timesheet->getBegin(); + $beginDate->setTimezone($timeZone); + $timesheet->setBegin($beginDate); + } + + if (null !== $timesheet->getEnd()) { + $endDate = clone $timesheet->getEnd(); + $endDate->setTimezone($timeZone); + $timesheet->setEnd($endDate); + } + + $timesheet->setTimezone($oldTimezone); + + return $timesheet; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + Type::overrideType(Type::DATETIME, DateTimeType::class); + + $io = new SymfonyStyle($input, $output); + + $start = $input->getOption('first-id'); + $end = $input->getOption('last-id'); + + $result = $this->getTimesheets($start, $end); + $amount = count($result); + + $answer = $io->ask(sprintf('This will update %s timesheet records, continue (y/n) ? ', $amount)); + + if ('y' !== $answer) { + $io->text('Aborting.'); + + return; + } + + $utc = new \DateTimeZone('UTC'); + $i = 0; + + /** @var Timesheet $timesheet */ + foreach ($result as $timesheet) { + $timesheet = $this->convertTimesheet($timesheet, $utc); + $this->repository->save($timesheet); + + if (++$i % 80 === 0) { + $io->writeln('. (' . $i . '/' . $amount . ')'); + } else { + $io->write('.'); + } + } + + $io->writeln('. (' . $i . '/' . $amount . ')'); + $io->writeln(''); + } +} diff --git a/src/Command/KimaiImporterCommand.php b/src/Command/KimaiImporterCommand.php index 71cecfc3..d16e40d6 100644 --- a/src/Command/KimaiImporterCommand.php +++ b/src/Command/KimaiImporterCommand.php @@ -19,6 +19,8 @@ use Doctrine\Common\Persistence\ObjectManager; use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Types\DateTimeType; +use Doctrine\DBAL\Types\Type; use Symfony\Bridge\Doctrine\RegistryInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -132,6 +134,10 @@ class KimaiImporterCommand extends Command */ protected function execute(InputInterface $input, OutputInterface $output) { + // do not convert the times, Kimai 1 stored them already in UTC + Type::overrideType(Type::DATETIME, DateTimeType::class); + + // don't calculate rates ... this was done in Kimai 1 $this->deactivateLifecycleCallbacks($this->getDoctrine()->getConnection()); $io = new SymfonyStyle($input, $output); diff --git a/src/Command/RunCodeSnifferCommand.php b/src/Command/RunCodeSnifferCommand.php index 85125727..4b75ae6d 100644 --- a/src/Command/RunCodeSnifferCommand.php +++ b/src/Command/RunCodeSnifferCommand.php @@ -20,16 +20,22 @@ use Symfony\Component\Console\Style\SymfonyStyle; */ class RunCodeSnifferCommand extends Command { + /** + * @var BashExecutor + */ + protected $executor; /** * @var string */ protected $rootDir = ''; /** + * @param BashExecutor $executor * @param string $projectDirectory */ - public function __construct($projectDirectory) + public function __construct(BashExecutor $executor, string $projectDirectory) { + $this->executor = $executor; $this->rootDir = realpath($projectDirectory); parent::__construct(); } @@ -42,7 +48,7 @@ class RunCodeSnifferCommand extends Command $this ->setName('kimai:phpcs') ->setDescription('Run PHP_CodeSniffer to check for the projects coding style') - ->addOption('fix', null, InputOption::VALUE_NONE, 'Fix all found problems (risky: modifies your files)') + ->addOption('fix', null, InputOption::VALUE_NONE, 'Fix all found problems') ->addOption('checkstyle', null, InputOption::VALUE_OPTIONAL, '') ; } @@ -55,7 +61,6 @@ class RunCodeSnifferCommand extends Command $io = new SymfonyStyle($input, $output); $filename = null; - ob_start(); $args = []; if (!$input->getOption('fix')) { @@ -78,13 +83,11 @@ class RunCodeSnifferCommand extends Command } } - $exitCode = 0; - passthru($this->rootDir . '/vendor/bin/php-cs-fixer fix ' . implode(' ', $args), $exitCode); - $result = ob_get_clean(); + $result = $this->executor->execute('/vendor/bin/php-cs-fixer fix ' . implode(' ', $args)); - $io->write($result); + $io->write($result->getResult()); - if ($exitCode > 0) { + if ($result->getExitCode() > 0) { $io->error( 'Found problems while checking your code styles' . (!empty($filename) ? '. Saved checkstyle data to: ' . $filename : '') diff --git a/src/Command/RunIntegrationTestsCommand.php b/src/Command/RunIntegrationTestsCommand.php index 5109e806..9dadc057 100644 --- a/src/Command/RunIntegrationTestsCommand.php +++ b/src/Command/RunIntegrationTestsCommand.php @@ -9,8 +9,6 @@ namespace App\Command; -use Symfony\Component\Console\Command\Command; - /** * Command used to run all integration tests. */ @@ -29,11 +27,10 @@ class RunIntegrationTestsCommand extends RunUnitTestsCommand } /** - * @param $directory * @return string */ - protected function createPhpunitCmdLine($directory) + protected function createPhpunitCmdLine() { - return $this->rootDir . '/bin/phpunit --group integration ' . $directory; + return '/bin/phpunit --group integration ' . $this->rootDir . '/tests'; } } diff --git a/src/Command/RunUnitTestsCommand.php b/src/Command/RunUnitTestsCommand.php index 90d98c31..76291dea 100644 --- a/src/Command/RunUnitTestsCommand.php +++ b/src/Command/RunUnitTestsCommand.php @@ -19,17 +19,22 @@ use Symfony\Component\Console\Style\SymfonyStyle; */ class RunUnitTestsCommand extends Command { + /** + * @var BashExecutor + */ + protected $executor; /** * @var string */ protected $rootDir; /** - * RunCodeSnifferCommand constructor. + * @param BashExecutor $executor * @param string $projectDirectory */ - public function __construct($projectDirectory) + public function __construct(BashExecutor $executor, string $projectDirectory) { + $this->executor = $executor; $this->rootDir = realpath($projectDirectory); parent::__construct(); } @@ -53,36 +58,24 @@ class RunUnitTestsCommand extends Command { $io = new SymfonyStyle($input, $output); - $this->executeTests($io, '/tests'); + $result = $this->executor->execute($this->createPhpunitCmdLine()); + + $io->write($result->getResult()); + + if ($result->getExitCode() > 0) { + $io->error('Found problems while running tests'); + + return; + } + + $io->success('All tests were successful'); } /** - * @param $directory * @return string */ - protected function createPhpunitCmdLine($directory) + protected function createPhpunitCmdLine() { - return $this->rootDir . '/bin/phpunit --exclude-group integration ' . $directory; - } - - /** - * @param string $directory - */ - protected function executeTests(SymfonyStyle $io, $directory) - { - $directory = $this->rootDir . $directory; - - $exitCode = 0; - ob_start(); - passthru($this->createPhpunitCmdLine($directory), $exitCode); - $result = ob_get_clean(); - - $io->write($result); - - if ($exitCode > 0) { - $io->error('Found problems while running tests at: ' . $directory); - } else { - $io->success('All tests performed good at: ' . $directory); - } + return '/bin/phpunit --exclude-group integration ' . $this->rootDir . '/tests'; } } diff --git a/src/DependencyInjection/AppExtension.php b/src/DependencyInjection/AppExtension.php index 04f1c644..4f2e3dcb 100644 --- a/src/DependencyInjection/AppExtension.php +++ b/src/DependencyInjection/AppExtension.php @@ -114,6 +114,7 @@ class AppExtension extends Extension implements PrependExtensionInterface */ protected function createTimesheetParameter(array $config, ContainerBuilder $container) { + $container->setParameter('kimai.timesheet.rules', $config['rules']); $container->setParameter('kimai.timesheet.rates', $config['rates']); $container->setParameter('kimai.timesheet.rounding', $config['rounding']); $container->setParameter('kimai.timesheet.duration_only', $config['duration_only']); diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index cbb09e35..822ad27a 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -113,28 +113,35 @@ class Configuration implements ConfigurationInterface ->end() ->arrayNode('active_entries') ->addDefaultsIfNotSet() - ->children() - ->integerNode('soft_limit') - ->defaultValue(1) - ->validate() - ->ifTrue(function ($value) { - return $value <= 0; - }) - ->thenInvalid('The soft_limit must be at least 1') - ->end() + ->children() + ->integerNode('soft_limit') + ->defaultValue(1) + ->validate() + ->ifTrue(function ($value) { + return $value <= 0; + }) + ->thenInvalid('The soft_limit must be at least 1') ->end() - ->integerNode('hard_limit') - ->defaultValue(1) - ->validate() - ->ifTrue(function ($value) { - return $value <= 0; - }) - ->thenInvalid('The hard_limit must be at least 1') - ->end() + ->end() + ->integerNode('hard_limit') + ->defaultValue(1) + ->validate() + ->ifTrue(function ($value) { + return $value <= 0; + }) + ->thenInvalid('The hard_limit must be at least 1') ->end() ->end() ->end() ->end() + ->arrayNode('rules') + ->addDefaultsIfNotSet() + ->children() + ->booleanNode('allow_future_times') + ->defaultTrue() + ->end() + ->end() + ->end() ->end() ; diff --git a/src/Doctrine/UTCDateTimeType.php b/src/Doctrine/UTCDateTimeType.php new file mode 100644 index 00000000..4add2675 --- /dev/null +++ b/src/Doctrine/UTCDateTimeType.php @@ -0,0 +1,74 @@ +setTimezone(self::getUtc()); + } + + return parent::convertToDatabaseValue($value, $platform); + } + + /** + * @return \DateTimeZone + */ + public static function getUtc() + { + return self::$utc ? self::$utc : self::$utc = new \DateTimeZone('UTC'); + } + + /** + * @param mixed $value + * @param AbstractPlatform $platform + * @return bool|\DateTime|false|mixed + * @throws ConversionException + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if (null === $value || $value instanceof \DateTime) { + return $value; + } + + $converted = \DateTime::createFromFormat( + $platform->getDateTimeFormatString(), + $value, + self::getUtc() + ); + + if (!$converted) { + throw ConversionException::conversionFailedFormat( + $value, + $this->getName(), + $platform->getDateTimeFormatString() + ); + } + + return $converted; + } +} diff --git a/src/Entity/Timesheet.php b/src/Entity/Timesheet.php index fef57e18..5e4c50cd 100644 --- a/src/Entity/Timesheet.php +++ b/src/Entity/Timesheet.php @@ -11,7 +11,6 @@ namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; -use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * Timesheet entity. @@ -25,7 +24,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; * ) * @ORM\Entity(repositoryClass="App\Repository\TimesheetRepository") * @ORM\HasLifecycleCallbacks() - * @Assert\Callback("validate") + * @App\Validator\Constraints\Timesheet */ class Timesheet { @@ -53,6 +52,18 @@ class Timesheet */ private $end; + /** + * @var string + * + * @ORM\Column(name="timezone", type="string", length=64, nullable=false) + */ + private $timezone; + + /** + * @var bool + */ + private $localized = false; + /** * @var int * @@ -137,11 +148,34 @@ class Timesheet return $this->id; } + /** + * Make sure begin and end date have the correct timezone. + * This will be called once for each item after being loaded from the database. + */ + protected function localizeDates() + { + if ($this->localized) { + return; + } + + if (null !== $this->begin) { + $this->begin->setTimeZone(new \DateTimeZone($this->timezone)); + } + + if (null !== $this->end) { + $this->end->setTimeZone(new \DateTimeZone($this->timezone)); + } + + $this->localized = true; + } + /** * @return \DateTime */ public function getBegin() { + $this->localizeDates(); + return $this->begin; } @@ -149,9 +183,10 @@ class Timesheet * @param \DateTime $begin * @return Timesheet */ - public function setBegin($begin) + public function setBegin(\DateTime $begin) { $this->begin = $begin; + $this->timezone = $begin->getTimezone()->getName(); return $this; } @@ -161,6 +196,8 @@ class Timesheet */ public function getEnd() { + $this->localizeDates(); + return $this->end; } @@ -168,21 +205,21 @@ class Timesheet * @param \DateTime $end * @return Timesheet */ - public function setEnd($end) + public function setEnd(?\DateTime $end) { $this->end = $end; if (null === $end) { $this->duration = 0; $this->rate = 0; + } else { + $this->timezone = $end->getTimezone()->getName(); } return $this; } /** - * Set duration - * * @param int $duration * @return Timesheet */ @@ -194,8 +231,7 @@ class Timesheet } /** - * Get duration - * Do not rely on the results of this method for active records. + * Do not rely on the results of this method for running records. * * @return int */ @@ -205,8 +241,6 @@ class Timesheet } /** - * Set user - * * @param User $user * @return Timesheet */ @@ -218,8 +252,6 @@ class Timesheet } /** - * Get user - * * @return User */ public function getUser() @@ -228,8 +260,6 @@ class Timesheet } /** - * Set activity - * * @param Activity $activity * @return Timesheet */ @@ -241,8 +271,6 @@ class Timesheet } /** - * Get Activity - * * @return Activity */ public function getActivity() @@ -373,74 +401,24 @@ class Timesheet } /** - * @param ExecutionContextInterface $context - * @param $payload + * @return string */ - public function validate(ExecutionContextInterface $context, $payload) + public function getTimezone(): string { - if (null === ($activity = $this->getActivity())) { - $context->buildViolation('A timesheet must have an activity.') - ->atPath('activity') - ->setTranslationDomain('validators') - ->addViolation(); - } + return $this->timezone; + } - if (null === ($project = $this->getProject())) { - $context->buildViolation('A timesheet must have a project.') - ->atPath('project') - ->setTranslationDomain('validators') - ->addViolation(); - } + /** + * BE WARNED: this method should NOT be used programmatically, there is very likely no reason for it! + * + * @deprecated since it was introduced, only meant for the initial migration. Will be removed with 1.0. + * @param string $timezone + * @return Timesheet + */ + public function setTimezone(string $timezone) + { + $this->timezone = $timezone; - if (null !== $activity && null !== $project) { - if (null !== $activity->getProject() && $activity->getProject() !== $project) { - $context->buildViolation('Project mismatch, project specific activity and timesheet project are different.') - ->atPath('project') - ->setTranslationDomain('validators') - ->addViolation(); - } - - if (null === $this->getEnd() && $activity->getVisible() === false) { - $context->buildViolation('Cannot start a disabled activity.') - ->atPath('activity') - ->setTranslationDomain('validators') - ->addViolation(); - } - - if (null === $this->getEnd() && $project->getVisible() === false) { - $context->buildViolation('Cannot start a disabled project.') - ->atPath('project') - ->setTranslationDomain('validators') - ->addViolation(); - } - - if (null === $this->getEnd() && $project->getCustomer()->getVisible() === false) { - $context->buildViolation('Cannot start a disabled customer.') - ->atPath('customer') - ->setTranslationDomain('validators') - ->addViolation(); - } - } - - if (null === $this->getBegin()) { - $context->buildViolation('You must submit a begin date.') - ->atPath('begin') - ->setTranslationDomain('validators') - ->addViolation(); - } else { - if (null !== $this->getBegin() && null !== $this->getEnd() && $this->getEnd()->getTimestamp() < $this->getBegin()->getTimestamp()) { - $context->buildViolation('End date must not be earlier then start date.') - ->atPath('end') - ->setTranslationDomain('validators') - ->addViolation(); - } - - if (time() < $this->getBegin()->getTimestamp()) { - $context->buildViolation('The begin date cannot be in the future.') - ->atPath('begin') - ->setTranslationDomain('validators') - ->addViolation(); - } - } + return $this; } } diff --git a/src/EventSubscriber/TimezoneSubscriber.php b/src/EventSubscriber/TimezoneSubscriber.php new file mode 100644 index 00000000..a37e5797 --- /dev/null +++ b/src/EventSubscriber/TimezoneSubscriber.php @@ -0,0 +1,58 @@ +storage = $tokenStorage; + } + + /** + * @return array + */ + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::REQUEST => ['setTimezone', 100], + ]; + } + + /** + * @param ShowUserEvent $event + */ + public function setTimezone(GetResponseEvent $event) + { + if (null === $this->storage->getToken()) { + return; + } + + /* @var $user User */ + $user = $this->storage->getToken()->getUser(); + $timezone = $user->getPreferenceValue('timezone', date_default_timezone_get()); + date_default_timezone_set($timezone); + } +} diff --git a/src/EventSubscriber/UserPreferenceSubscriber.php b/src/EventSubscriber/UserPreferenceSubscriber.php index c2bc7e6a..6eb5974d 100644 --- a/src/EventSubscriber/UserPreferenceSubscriber.php +++ b/src/EventSubscriber/UserPreferenceSubscriber.php @@ -21,6 +21,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\MoneyType; +use Symfony\Component\Form\Extension\Core\Type\TimezoneType; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Validator\Constraints\Range; @@ -91,6 +92,11 @@ class UserPreferenceSubscriber implements EventSubscriberInterface ->setEnabled($enableHourlyRate) ->addConstraint(new Range(['min' => 0])), + (new UserPreference()) + ->setName('timezone') + ->setValue(date_default_timezone_get()) + ->setType(TimezoneType::class), + (new UserPreference()) ->setName('language') ->setValue('en') diff --git a/src/Export/Renderer/AbstractSpreadsheetRenderer.php b/src/Export/Renderer/AbstractSpreadsheetRenderer.php index 5fa4d647..134d116c 100644 --- a/src/Export/Renderer/AbstractSpreadsheetRenderer.php +++ b/src/Export/Renderer/AbstractSpreadsheetRenderer.php @@ -54,7 +54,7 @@ abstract class AbstractSpreadsheetRenderer */ protected function getFormattedDateTime(\DateTime $date) { - return $this->dateExtension->dateShort($date) . ' ' . date('H:i', $date->getTimestamp()); + return $this->dateExtension->dateShort($date) . ' ' . $this->dateExtension->time($date); } /** diff --git a/src/Form/TimesheetEditForm.php b/src/Form/TimesheetEditForm.php index 223d1cc3..9055189b 100644 --- a/src/Form/TimesheetEditForm.php +++ b/src/Form/TimesheetEditForm.php @@ -103,6 +103,7 @@ class TimesheetEditForm extends AbstractType } else { $builder->add('end', DateTimePickerType::class, [ 'label' => 'label.end', + 'required' => false, ]); } @@ -129,26 +130,22 @@ class TimesheetEditForm extends AbstractType $projectOptions['group_by'] = null; } - if ($this->projects->countProject(true) > 1) { - $projectOptions['placeholder'] = null; - } else { + if ($this->projects->countProject(true) <= 1) { $projectOptions['group_by'] = null; } $builder - ->add( - 'project', - ProjectType::class, - array_merge($projectOptions, [ - 'activity_enabled' => true, - // documentation is for NelmioApiDocBundle - 'documentation' => [ - 'type' => 'integer', - 'description' => 'Project ID', - ], - 'query_builder' => function (ProjectRepository $repo) use ($project, $customer) { - return $repo->builderForEntityType($project, $customer); - }, + ->add('project', ProjectType::class, array_merge($projectOptions, [ + 'placeholder' => '', + 'activity_enabled' => true, + // documentation is for NelmioApiDocBundle + 'documentation' => [ + 'type' => 'integer', + 'description' => 'Project ID', + ], + 'query_builder' => function (ProjectRepository $repo) use ($project, $customer) { + return $repo->builderForEntityType($project, $customer); + }, ]) ); @@ -162,6 +159,7 @@ class TimesheetEditForm extends AbstractType } $event->getForm()->add('project', ProjectType::class, [ + 'placeholder' => '', 'activity_enabled' => true, 'group_by' => null, 'query_builder' => function (ProjectRepository $repo) use ($data, $project) { @@ -174,7 +172,7 @@ class TimesheetEditForm extends AbstractType $builder ->add('activity', ActivityType::class, [ // documentation is for NelmioApiDocBundle - 'placeholder' => null, + 'placeholder' => '', 'documentation' => [ 'type' => 'integer', 'description' => 'Activity ID', @@ -195,7 +193,7 @@ class TimesheetEditForm extends AbstractType } $event->getForm()->add('activity', ActivityType::class, [ - 'placeholder' => null, + 'placeholder' => '', 'query_builder' => function (ActivityRepository $repo) use ($data, $activity) { return $repo->builderForEntityType($activity, $data['project']); }, diff --git a/src/Invoice/Renderer/AbstractRenderer.php b/src/Invoice/Renderer/AbstractRenderer.php index 65b53e70..dbc0bd28 100644 --- a/src/Invoice/Renderer/AbstractRenderer.php +++ b/src/Invoice/Renderer/AbstractRenderer.php @@ -53,6 +53,15 @@ abstract class AbstractRenderer return $this->dateExtension->dateShort($date); } + /** + * @param \DateTime $date + * @return mixed + */ + protected function getFormattedTime(\DateTime $date) + { + return $this->dateExtension->time($date); + } + /** * @param $amount * @param $currency diff --git a/src/Invoice/Renderer/RendererTrait.php b/src/Invoice/Renderer/RendererTrait.php index 2a51c0a8..1f4fe5ea 100644 --- a/src/Invoice/Renderer/RendererTrait.php +++ b/src/Invoice/Renderer/RendererTrait.php @@ -49,6 +49,12 @@ trait RendererTrait */ abstract protected function getFormattedDateTime(\DateTime $date); + /** + * @param \DateTime $date + * @return mixed + */ + abstract protected function getFormattedTime(\DateTime $date); + /** * @param $amount * @return mixed @@ -172,10 +178,10 @@ trait RendererTrait 'entry.duration' => $timesheet->getDuration(), 'entry.duration_minutes' => number_format($timesheet->getDuration() / 60), 'entry.begin' => $this->getFormattedDateTime($begin), - 'entry.begin_time' => date('H:i', $begin->getTimestamp()), + 'entry.begin_time' => $this->getFormattedTime($begin), 'entry.begin_timestamp' => $begin->getTimestamp(), 'entry.end' => $this->getFormattedDateTime($end), - 'entry.end_time' => date('H:i', $end->getTimestamp()), + 'entry.end_time' => $this->getFormattedTime($end), 'entry.end_timestamp' => $end->getTimestamp(), 'entry.date' => $this->getFormattedDateTime($begin), 'entry.user_id' => $user->getId(), diff --git a/src/Migrations/Version20190201150324.php b/src/Migrations/Version20190201150324.php new file mode 100644 index 00000000..662e72b4 --- /dev/null +++ b/src/Migrations/Version20190201150324.php @@ -0,0 +1,71 @@ +getPlatform(); + + if (!in_array($platform, ['sqlite', 'mysql'])) { + $this->abortIf(true, 'Unsupported database platform: ' . $platform); + } + + $timesheet = $this->getTableName('timesheet'); + $timezone = date_default_timezone_get(); + + if ($platform === 'sqlite') { + $this->addSql('ALTER TABLE ' . $timesheet . ' ADD COLUMN timezone VARCHAR(64) DEFAULT NULL'); + } else { + $this->addSql('ALTER TABLE ' . $timesheet . ' ADD timezone VARCHAR(64) NOT NULL'); + } + + $this->addSql('UPDATE ' . $timesheet . ' SET timezone = "' . $timezone . '"'); + } + + public function down(Schema $schema): void + { + $platform = $this->getPlatform(); + + if (!in_array($platform, ['sqlite', 'mysql'])) { + $this->abortIf(true, 'Unsupported database platform: ' . $platform); + } + + $timesheet = $this->getTableName('timesheet'); + + if ($platform === 'sqlite') { + $this->addSql('DROP INDEX IDX_4F60C6B1166D1F9C'); + $this->addSql('DROP INDEX IDX_4F60C6B18D93D649'); + $this->addSql('DROP INDEX IDX_4F60C6B181C06096'); + $this->addSql('CREATE TEMPORARY TABLE __temp__' . $timesheet . ' AS SELECT id, user, activity_id, project_id, start_time, end_time, duration, description, rate, fixed_rate, hourly_rate, exported FROM ' . $timesheet); + $this->addSql('DROP TABLE ' . $timesheet); + $this->addSql('CREATE TABLE ' . $timesheet . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user INTEGER NOT NULL, activity_id INTEGER NOT NULL, project_id INTEGER NOT NULL, start_time DATETIME NOT NULL --(DC2Type:datetime) + , end_time DATETIME DEFAULT NULL --(DC2Type:datetime) + , duration INTEGER DEFAULT NULL, description CLOB DEFAULT NULL, rate NUMERIC(10, 2) NOT NULL, fixed_rate NUMERIC(10, 2) DEFAULT NULL, hourly_rate NUMERIC(10, 2) DEFAULT NULL, exported BOOLEAN NOT NULL DEFAULT false)'); + $this->addSql('INSERT INTO ' . $timesheet . ' (id, user, activity_id, project_id, start_time, end_time, duration, description, rate, fixed_rate, hourly_rate, exported) SELECT id, user, activity_id, project_id, start_time, end_time, duration, description, rate, fixed_rate, hourly_rate, exported FROM __temp__' . $timesheet); + $this->addSql('DROP TABLE __temp__' . $timesheet); + $this->addSql('CREATE INDEX IDX_4F60C6B1166D1F9C ON ' . $timesheet . ' (project_id)'); + $this->addSql('CREATE INDEX IDX_4F60C6B18D93D649 ON ' . $timesheet . ' (user)'); + $this->addSql('CREATE INDEX IDX_4F60C6B181C06096 ON ' . $timesheet . ' (activity_id)'); + } else { + $this->addSql('ALTER TABLE ' . $timesheet . ' DROP timezone'); + } + } +} diff --git a/src/Repository/TimesheetRepository.php b/src/Repository/TimesheetRepository.php index 4aa9653c..a6cb01d7 100644 --- a/src/Repository/TimesheetRepository.php +++ b/src/Repository/TimesheetRepository.php @@ -29,6 +29,18 @@ class TimesheetRepository extends AbstractRepository public const STATS_QUERY_ACTIVE = 'active'; public const STATS_QUERY_MONTHLY = 'monthly'; + /** + * @param Timesheet $timesheet + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + */ + public function save(Timesheet $timesheet) + { + $entityManager = $this->getEntityManager(); + $entityManager->persist($timesheet); + $entityManager->flush(); + } + /** * @param Timesheet $entry * @return bool diff --git a/src/Twig/DateExtensions.php b/src/Twig/DateExtensions.php index 2dcab2ca..c17f6fef 100644 --- a/src/Twig/DateExtensions.php +++ b/src/Twig/DateExtensions.php @@ -40,6 +40,7 @@ class DateExtensions extends \Twig_Extension new TwigFilter('month_name', [$this, 'monthName']), new TwigFilter('date_short', [$this, 'dateShort']), new TwigFilter('date_time', [$this, 'dateTime']), + new TwigFilter('time', [$this, 'time']), ]; } @@ -65,6 +66,15 @@ class DateExtensions extends \Twig_Extension return date_format($date, $format); } + /** + * @param DateTime $date + * @return string + */ + public function time(DateTime $date) + { + return date_format($date, 'H:i'); + } + /** * @param \DateTime $date * @return string diff --git a/src/Twig/Extensions.php b/src/Twig/Extensions.php index 274db78e..fadc4668 100644 --- a/src/Twig/Extensions.php +++ b/src/Twig/Extensions.php @@ -86,7 +86,7 @@ class Extensions extends \Twig_Extension 'user' => 'fas fa-user', 'visibility' => 'far fa-eye', 'settings' => 'fas fa-wrench', - 'export' => 'fas fa-database', + 'export' => 'fas fa-file-export', 'pdf' => 'fas fa-file-pdf', 'csv' => 'fas fa-table', 'ods' => 'fas fa-table', diff --git a/src/Utils/Markdown.php b/src/Utils/Markdown.php index bc05c4b8..a1c12e8c 100644 --- a/src/Utils/Markdown.php +++ b/src/Utils/Markdown.php @@ -15,7 +15,7 @@ namespace App\Utils; class Markdown { /** - * @var MarkdownParser + * @var ParsedownExtension */ private $parser; diff --git a/src/Validator/Constraints/Timesheet.php b/src/Validator/Constraints/Timesheet.php new file mode 100644 index 00000000..ded5dca2 --- /dev/null +++ b/src/Validator/Constraints/Timesheet.php @@ -0,0 +1,49 @@ + 'You must submit a begin date.', + self::END_BEFORE_BEGIN_ERROR => 'End date must not be earlier then start date.', + self::BEGIN_IN_FUTURE_ERROR => 'The begin date cannot be in the future.', + self::MISSING_ACTIVITY_ERROR => 'A timesheet must have an activity.', + self::MISSING_PROJECT_ERROR => 'A timesheet must have a project.', + self::ACTIVITY_PROJECT_MISMATCH_ERROR => 'Project mismatch, project specific activity and timesheet project are different.', + self::DISABLED_ACTIVITY_ERROR => 'Cannot start a disabled activity.', + self::DISABLED_PROJECT_ERROR => 'Cannot start a disabled project.', + self::DISABLED_CUSTOMER_ERROR => 'Cannot start a disabled customer.', + ]; + + public $message = 'This timesheet has invalid settings.'; + + public function getTargets() + { + return self::CLASS_CONSTRAINT; + } +} diff --git a/src/Validator/Constraints/TimesheetValidator.php b/src/Validator/Constraints/TimesheetValidator.php new file mode 100644 index 00000000..788095cf --- /dev/null +++ b/src/Validator/Constraints/TimesheetValidator.php @@ -0,0 +1,157 @@ +rules = $ruleset; + } + + /** + * @param string $key + * @param null $default + * @return mixed|null + */ + protected function getRule(string $key, $default = null) + { + if (!isset($this->rules[$key])) { + return $default; + } + + return $this->rules[$key]; + } + + /** + * @param Timesheet $value + * @param Constraint $constraint + */ + public function validate($value, Constraint $constraint) + { + if (!($constraint instanceof TimesheetConstraint)) { + throw new UnexpectedTypeException($constraint, __NAMESPACE__ . '\Timesheet'); + } + + if (!is_object($value) || !($value instanceof Timesheet)) { + return; + } + + $this->validateBeginAndEnd($value, $this->context); + $this->validateActivityAndProject($value, $this->context); + } + + /** + * @param Timesheet $timesheet + * @param ExecutionContextInterface $context + */ + protected function validateBeginAndEnd(Timesheet $timesheet, ExecutionContextInterface $context) + { + if (null === $timesheet->getBegin()) { + $context->buildViolation('You must submit a begin date.') + ->atPath('begin') + ->setTranslationDomain('validators') + ->setCode(TimesheetConstraint::MISSING_BEGIN_ERROR) + ->addViolation(); + + return; + } + + if (null !== $timesheet->getBegin() && null !== $timesheet->getEnd() && $timesheet->getEnd()->getTimestamp() < $timesheet->getBegin()->getTimestamp()) { + $context->buildViolation('End date must not be earlier then start date.') + ->atPath('end') + ->setTranslationDomain('validators') + ->setCode(TimesheetConstraint::END_BEFORE_BEGIN_ERROR) + ->addViolation(); + } + + if (false === $this->getRule('allow_future_times', true) && time() < $timesheet->getBegin()->getTimestamp()) { + $context->buildViolation('The begin date cannot be in the future.') + ->atPath('begin') + ->setTranslationDomain('validators') + ->setCode(TimesheetConstraint::BEGIN_IN_FUTURE_ERROR) + ->addViolation(); + } + } + + /** + * @param Timesheet $timesheet + * @param ExecutionContextInterface $context + */ + protected function validateActivityAndProject(Timesheet $timesheet, ExecutionContextInterface $context) + { + if (null === ($activity = $timesheet->getActivity())) { + $context->buildViolation('A timesheet must have an activity.') + ->atPath('activity') + ->setTranslationDomain('validators') + ->setCode(TimesheetConstraint::MISSING_ACTIVITY_ERROR) + ->addViolation(); + } + + if (null === ($project = $timesheet->getProject())) { + $context->buildViolation('A timesheet must have a project.') + ->atPath('project') + ->setTranslationDomain('validators') + ->setCode(TimesheetConstraint::MISSING_PROJECT_ERROR) + ->addViolation(); + } + + if (null === $activity || null === $project) { + return; + } + + if (null !== $activity->getProject() && $activity->getProject() !== $project) { + $context->buildViolation('Project mismatch, project specific activity and timesheet project are different.') + ->atPath('project') + ->setTranslationDomain('validators') + ->setCode(TimesheetConstraint::ACTIVITY_PROJECT_MISMATCH_ERROR) + ->addViolation(); + } + + if (null === $timesheet->getEnd() && $activity->getVisible() === false) { + $context->buildViolation('Cannot start a disabled activity.') + ->atPath('activity') + ->setTranslationDomain('validators') + ->setCode(TimesheetConstraint::DISABLED_ACTIVITY_ERROR) + ->addViolation(); + } + + if (null === $timesheet->getEnd() && $project->getVisible() === false) { + $context->buildViolation('Cannot start a disabled project.') + ->atPath('project') + ->setTranslationDomain('validators') + ->setCode(TimesheetConstraint::DISABLED_PROJECT_ERROR) + ->addViolation(); + } + + if (null === $timesheet->getEnd() && $project->getCustomer()->getVisible() === false) { + $context->buildViolation('Cannot start a disabled customer.') + ->atPath('customer') + ->setTranslationDomain('validators') + ->setCode(TimesheetConstraint::DISABLED_CUSTOMER_ERROR) + ->addViolation(); + } + } +} diff --git a/src/Voter/ActivityVoter.php b/src/Voter/ActivityVoter.php index 91d099ba..6911fd26 100644 --- a/src/Voter/ActivityVoter.php +++ b/src/Voter/ActivityVoter.php @@ -38,7 +38,7 @@ class ActivityVoter extends AbstractVoter */ protected function supports($attribute, $subject) { - if (!$subject instanceof Activity) { + if (!($subject instanceof Activity)) { return false; } @@ -63,10 +63,6 @@ class ActivityVoter extends AbstractVoter return false; } - if ($subject instanceof Activity) { - return $this->hasRolePermission($user, $attribute . '_activity'); - } - - return false; + return $this->hasRolePermission($user, $attribute . '_activity'); } } diff --git a/src/Voter/CustomerVoter.php b/src/Voter/CustomerVoter.php index 5b3c6402..7ea6e163 100644 --- a/src/Voter/CustomerVoter.php +++ b/src/Voter/CustomerVoter.php @@ -38,7 +38,7 @@ class CustomerVoter extends AbstractVoter */ protected function supports($attribute, $subject) { - if (!$subject instanceof Customer) { + if (!($subject instanceof Customer)) { return false; } @@ -63,10 +63,6 @@ class CustomerVoter extends AbstractVoter return false; } - if ($subject instanceof Customer) { - return $this->hasRolePermission($user, $attribute . '_customer'); - } - - return false; + return $this->hasRolePermission($user, $attribute . '_customer'); } } diff --git a/src/Voter/InvoiceTemplateVoter.php b/src/Voter/InvoiceTemplateVoter.php index 44a36414..a5a1214e 100644 --- a/src/Voter/InvoiceTemplateVoter.php +++ b/src/Voter/InvoiceTemplateVoter.php @@ -38,7 +38,7 @@ class InvoiceTemplateVoter extends AbstractVoter */ protected function supports($attribute, $subject) { - if (!$subject instanceof InvoiceTemplate) { + if (!($subject instanceof InvoiceTemplate)) { return false; } @@ -63,10 +63,6 @@ class InvoiceTemplateVoter extends AbstractVoter return false; } - if ($subject instanceof InvoiceTemplate) { - return $this->hasRolePermission($user, $attribute . '_invoice_template'); - } - - return false; + return $this->hasRolePermission($user, $attribute . '_invoice_template'); } } diff --git a/src/Voter/ProjectVoter.php b/src/Voter/ProjectVoter.php index 4b26e6da..7b821aa8 100644 --- a/src/Voter/ProjectVoter.php +++ b/src/Voter/ProjectVoter.php @@ -38,7 +38,7 @@ class ProjectVoter extends AbstractVoter */ protected function supports($attribute, $subject) { - if (!$subject instanceof Project) { + if (!($subject instanceof Project)) { return false; } @@ -63,10 +63,6 @@ class ProjectVoter extends AbstractVoter return false; } - if ($subject instanceof Project) { - return $this->hasRolePermission($user, $attribute . '_project'); - } - - return false; + return $this->hasRolePermission($user, $attribute . '_project'); } } diff --git a/src/Voter/TimesheetVoter.php b/src/Voter/TimesheetVoter.php index 6ae773d8..69be44b8 100644 --- a/src/Voter/TimesheetVoter.php +++ b/src/Voter/TimesheetVoter.php @@ -9,7 +9,6 @@ namespace App\Voter; -use App\Entity\Activity; use App\Entity\Timesheet; use App\Entity\User; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -49,7 +48,7 @@ class TimesheetVoter extends AbstractVoter */ protected function supports($attribute, $subject) { - if (!$subject instanceof Timesheet) { + if (!($subject instanceof Timesheet)) { return false; } @@ -62,7 +61,7 @@ class TimesheetVoter extends AbstractVoter /** * @param string $attribute - * @param Timesheet|Activity $subject + * @param Timesheet $subject * @param TokenInterface $token * @return bool */ @@ -74,10 +73,6 @@ class TimesheetVoter extends AbstractVoter return false; } - if (!($subject instanceof Timesheet)) { - return false; - } - $permission = ''; switch ($attribute) { diff --git a/src/Voter/UserVoter.php b/src/Voter/UserVoter.php index be8650bf..35f389da 100644 --- a/src/Voter/UserVoter.php +++ b/src/Voter/UserVoter.php @@ -44,11 +44,11 @@ class UserVoter extends AbstractVoter */ protected function supports($attribute, $subject) { - if (!in_array($attribute, self::ALLOWED_ATTRIBUTES)) { + if (!($subject instanceof User)) { return false; } - if (!($subject instanceof User)) { + if (!in_array($attribute, self::ALLOWED_ATTRIBUTES)) { return false; } diff --git a/templates/admin/timesheet.html.twig b/templates/admin/timesheet.html.twig index e8915c8e..08270c42 100644 --- a/templates/admin/timesheet.html.twig +++ b/templates/admin/timesheet.html.twig @@ -52,12 +52,12 @@test
', $sut->toHtml('*test*')); $this->assertEquals('foo bar
+asdfasdfa
+ssdfsdf
+sdfsdf asdfasdf asdfasdf
+aasdfasdf
+EOT; + + $markdown = <<