Orderassets/css/social-admin.css000064400000006704147577535410011741 0ustar00.loginizer-social-wrapper{ display:flex; flex-direction:row; gap:20px; flex-wrap:wrap; position:relative; width:100%; cursor: drag; } .loginizer-social-provider{ width:30%; height:220px; border-radius:5px; } .loginizer-social-logo{ position:relative; height: 75%; background:red; display:flex; align-items:center; justify-content:center; border-radius: 5px 5px 0 0; } .loginizer-social-grip{ position:absolute; top: 8px; left: 8px; cursor:move; } .loginizer-social-logo img{ border-radius:5px; } .loginizer-social-provider-action{ display:flex; align-items:center; justify-content:space-between; padding:0 10px; height:25%; background-color: #FFF; border-radius: 0 0 5px 5px; } .loginizer-social-wrapper .loginizer-sortable-placeholder { position:relative; display:flex; align-items:center; justify-content:center; border-radius: 5px 5px 0 0; border: 2px dashed #e0e0e0; box-shadow:none; width:30%; background-color: #FAFAFA; } .loginizer-social-wrapper .loginizer-sortable-placeholder::before { content: "Drop Here"; position:absolute; font-family:monospace; left: 50%; transform: translateX(-50%); color:#CFCFCF; } .loginizer-general-settings label{ margin-right:20px; } .loginizer-social-provider-state{ position:absolute; top:8px; right:0px; background-color: #FFF; color: #000; border-radius: 20px 0 0 20px; padding: 2px 6px; line-height:1.2; vertical-align: middle; text-align:center; font-weight:500; box-shadow: -1px 1px 1px 1px #3b3b3b36; } .loginizer-social-provider-state::before{ content: ''; display:inline-flex; width:10px; height:10px; border-radius:20px; margin-right:3px; } .loginizer-social-provider-state-enabled::before{ background-color:#3cff3c; } .loginizer-social-provider-state-pending::before{ background-color:#FFAAAA; } .loginizer-social-provider-state-disabled::before{ background-color:#f53333; } .loginizer-social-saving-order{ position:absolute; bottom:8px; left:10px; display:none; color:#FFF; font-weight:500; } .loginizer-social-login-settings{ display:flex; flex-direction:row; } .loginizer-v-tab{ display:flex; flex-direction:column; width:20%; } .loginizer-v-tab label{ position:relative; padding: 20px; color: black; user-select:none; text-decoration:none; } .loginizer-v-tab label input{ position:absolute; display:none; } .loginizer-v-tab label:has(input:checked){ background-color:white; } .loginizer-v-tab label:has(input:not(:checked)){ cursor:pointer; } .lz-v-tab-content{ width:100%; background-color:white; padding: 0 10px 20px 20px; } .lz-v-tab-content #lz-social-general-settings, .lz-v-tab-content #lz-social-login-settings, .lz-v-tab-content #lz-social-woocommerce-settings, .lz-v-tab-content #lz-social-comment-settings{ display:none; } .loginizer-v-tab:has(label > input[value="general"]:checked) ~ .lz-v-tab-content #lz-social-general-settings, .loginizer-v-tab:has(label > input[value="login"]:checked) ~ .lz-v-tab-content #lz-social-login-settings, .loginizer-v-tab:has(label > input[value="woocommerce"]:checked) ~ .lz-v-tab-content #lz-social-woocommerce-settings, .loginizer-v-tab:has(label > input[value="comment"]:checked) ~ .lz-v-tab-content #lz-social-comment-settings{ display:block; } .loginizer-social-test-alert{ background-color:#fff3cd; color:#664d03; border: 2px solid #ffe69c; padding:0 20px 20px 20px; border-radius:6px; width:500px; margin:10px 0; } assets/images/social/Facebook-light.png000064400000004066147577535410014141 0ustar00PNG  IHDRddpTsRGB, pHYs.#.#x?vIDATxylEAM4 H@4AL@rIJHD0@P)BbhaF!XD@ F]@ I)wK:t_پvvf{%hٙof8H܃< G#kar ns9E"Bd<i*šٕ)EN#UB#bu$SjX)Nd?=h{D@ FCY÷B"$ qd"-b׫P.ZS]_>XDbR  ڥLi3>"R]B#HKp0>dҠBEqyi~}~d{'Y2tcX *-l*S !S]Y2Yܭ )IC~GڪPX]TjN"=UӠ2t>("Jdkħ㖂ڗ`>Y<8]la堕Va*tɫֱ,7_}(VGHuA h3@53-3z"dDsSPDrTMk7kt {GA V]5i[~ʠ>2\!2|PtߞL: !~`~F]'$hp  gYZyU)%`uhs8_O!UKp V!Ev+nLW22BrKPtZɠ,t`j6DuMw2l!tD#XB2~"ʉV!v FBFŔ"m*Y V,. Ьȭ{cwr#$4h Ғ;PHr/2BIJ%zMa/2B@PmBU!a |m83Bu^F7#7琎Be8e 5(2RzgSDnN2R^f|'}!’BHF)2jHmdFJb:2#duSqONHB!4 w47?|K p3cOgTBsS4" \tR]X ad27֢EzF)txF Zv* EKPݍd~!ki!F*G|tڷ%7a@Y:oL+z/H hwu[ǿQX^j2\ y{C9,fq<3TKn՞84e5Rvm$NP0M_p'/IDATxcEdza<@8 `8BCPC 4){Giқtމ(AD]D@lb {s7;wd~μ۝7f#" M,$,pED(htxbë%by) u2a0 u !3*VmDAQťT P QIա֨kԆaJ!uMoLQAHcWC̊ƼuFP17k&D0+ZSqi6Lěfu0hW)}tCDL7&]9[SuA,.D4k3;^p*ٽ%1JU?Xu}7+|1s4[ ̆ =d0ڦ?Pᇈ!h2Kp[l{0P!iEwtPc$x}hv,Qh7I?Y0ejpToc?PʐxD|ygl}j" <+[Aӟ]) >\Ռ%y!LR_84QcEjFDU"wV*\jDX A w5>}g.#ò_C6k֮ hHu 06ʡ%v4K"+cWݴ2LӭiTfգB-/Pݪa(!"E]6D/Rءc!b'lwqq7 =썄OQ42s=-!J2 ֚ƽNO+rD= "&.RT3]伄c/0,!ӃDEǮ`αcXCZNHG+y-"^:SO)꼫[=:4Q:Yj 'M?QR9"18` 4Za' -D=}1C]Ĉ댡I+ 2l bm"9Fx8+1$q "WhH:ص8v|7+`  t5 "*3Mdj^+ 1b46r "VŸZU섂$.չ@ !b1,iL@|;i\᲍ N^-6D P_+Bq@F2k[WoKɴ^Aӛxf$ oB 2@RFy̋"|m)%M.o2[\-~wP-AF[}FczHj!i7xbЗhVut)v@Ri¦4bm^cQ$W-!L^U "ݦYDWrX_W)x!.if!J 믂q*;`T[C]I+D\9FpJir_J^s3 ?;&zKu?$Ci9%MBcjچjot-Q^F)v^V(Y+ w胋a|i]4ԂbH]z!Si0oX 1S {o .\'N:{x M?׹H5IENDB`assets/images/social/GitHub.png000064400000004741147577535410012505 0ustar00PNG  IHDRdbe{sRGB, pHYs7]7]F]sPLTEzQtRNS +Kk̽jG)ZØUT3 Nfnl4;0ΟBֹA PW$M:s!\xۏ^Ğ8by& DI*>i]u6-/("vJ%O.Srw`<[_dgeQL R2FXoH=@,paY׳n&W*IDATxCG'BQnE9Z"X"G(V rZI9D*PՊmj JQzI\f7{[}̛7I* PѨT+m.u#"#ׯ Qٰq-G[m!>!͋/% !('k bRR  i2ƌL123L2lJD*d4ΗX (XEL@pJJH¤,A8@)_mrAۄ ]r0Q^gvZrɰ`c/;7۷R›^t fKAUCPuMF8Dl]}FVa0&cG\,ӒIJڛH{+oMo Z[(GYcYM0loCk^Gzzw iwõ% {ƾBO L׻ϲ`BVqd10 [Qcidh=- A@96F r02LD G߾RdXY G33H%.C#ks0}ڔ-^:Wuh B)Xa/WָX¤^7 Swsݞ&FNlˈ:-݁"ox1;A1r[ fH WfGa| (0_!t4*G`zGS蹇 Lʿ>M/.kF;\=^T"2O=s*ce/ E1bqCNGǓSwbiMP|m] e ݁"tq8x88n\_M|T~/ G^wo0ޱ,rw1oA oL絆35*nZwN=׺ED5 hieyMы,w]AH]Xw:#{a\" yT,M&b{jvFR?u񤈔iB1ݛ͹T=?ߡK#f=,,q^W[n#J! S,r?Y- Uɽ_zƷtN/O+VBReǣ5 7CYѠ9pf>/Ws"(IENDB`assets/images/social/Facebook.png000064400000004044147577535410013030 0ustar00PNG  IHDRddGfJ;wOdKM=IDATxCSU3 &ӹsE6p2` P̚S4|KH{s&fՔ -+"4{S{ú{ysylb_0u}zQTQR:slr2*sy&^K_E3TVU/6I<&Y,4A,/')ZQcLkpD*%5U?]PSn$l+Wɬ^VX^HfFwyܘAic9RijVj6m-[>Ux)S6=izin%'RZ-1Tep<,mh1}37Hl}φ(6gEC;⛘xpLj Ůng k+_15pwQyE[bCwU-W{5%+1Fq!Ux`zQ:d7 c]Eb jGZ9Oʊ7'Ɨ#v- o~ ^9c%yGwGhQbMMO"ї@{[(4B{{/aR)K4<jـGOAf:b=B %`)?1t.BcY!JE 5ȕ8P  )V#I5W&3-A|_e2K@z52{Ȏ&2+o#.U Kr+o$pGJvKVTG#jbT`{zVTy5P13T⥹DDz\"קt4bP; eff,"AA;,͞Ұ:GL_ ]Y CLCfcH|jdӴ nqrlc%yg5j# dv,(D!? hH3 Wd bCUvG<&,ONHG!t ϫ18ׇ6k=F _Ɛ߆t~dC=]l&p!xgx^leQ߃q$b0F$zXGof dw,!?CW/ R )HA A{W:mssWw?/\4Hsqt 5kh!q4cRABC $6H€#)$ 06c@@"#70λpL."Ơ's\)w I2 [E5U%ܺ JӘ,;pc#ZRujSYCBgʈ @=IDATxZCGHHBB80sfꩁ08RܼZ̅^P ÿxU/UwI>vu6sAjZ=*8][&+rWڪ|ץi7:<~׊djo0H8rsޥ*&S wEZ,šo6 9`)n5#HޣDf`]'hƌc9XudVWR< x-]?~ސ 7'(˰eK].޻L*fQ{%|>M|{޽Owy#M$އu\qx4Ty{?*?YQva( ~9]]ɨgHkb{c/ 6y2di-U49)/e_5`dU-{*ḽ k Pi͜H QN"nNSQ}PpꁜK+ЉT %#°G&ioLm̥JpR{C aI*1mBS"^-qFAD'08JF9?ߡlOkL=%  y9wab =.kqQ ",<cw9\jb=ZK Hϳ%݂ͬ/-vb-a)Qb⋭gR  Z,G^_j칸A2 .8(YX𥅖&*A#VjHrhA߂3f2 a42Jt41uN\WP{-Y/ G'pڔzEXVQF%%7DƩ0J :YDF#43ieg|\]|ԥ] {GdU ޘ4|=7lJI=/x8iz& %3r{-a:8HB}*H 'hSZ!knP U/l#O2@PDC8^N >hjeǛ1Q%!.@JuD7C ~%M/|YZ+Lm(P /N%(%RD/- P)Q%A0D%7 ïP'wAJ?L d->RQ~-a(|yMGա VGD)|y)WXZptP 5c?C~ C`"Cb`^"8Lg,$F-{ 'F9JAR - JRA,〃e q>-t*H_ 0e*'X 't#[_tD =`#l ln^N}M JQKHCw-+m8itFZ>Ov9R״D˃hiic_+=5'iAI4*q:$X y 12b!٣9⬋Kd+ϨQKqGt%ohl)DhGr?͖;n6hh i&!`6",Uw-Qa4iHւO4kjMxc&A= :g6~Zv=n-H78lF PkfΎS6I۲j}l}٭mY`| }űҨ0 f*'՘qQPV9M8VcBK?|AqQӕ)ߩC NY'%c_m>?X >?"p;2FIENDB`assets/images/social/LinkedInOpenID.png000064400000001764147577535410014061 0ustar00PNG  IHDRTU3VsRGB, pHYs.#.#x?v)PLTEjctRNSoDm`4AV /S$tЏmBVttD\ M4zug&ecLDM_W׭?U41h T5Q_A'77o!U"Gy?S*FżJdy*;GIENDB`assets/images/social/Discord.png000064400000005507147577535410012713 0ustar00PNG  IHDRdLsRGB, pHYs55^e IDATx]{pTQ&P(  mm냶vmeiZ yLbt"oJ!EB{yw y{Ce~|;"~pO1gɌF!fu tSJ] ;JW珝&4?|H#00;Y'Z*> >{'Q3 ʂ"kont#A!c[1K4@"(㤥>ZBxj|@}L甜%oxW4wY[Ogl. #9o*k%D٪Tyв?.ƼLm;`\YE mqZ@ h?5szU!I)o-}\5y <ħ3b.n">+"<`#Z|pCTPyM)OaA"0n*!B:PWg_k:2#r @ڟa!(["N>fޒ3{8bs:@EtJ7``}Ҙx,.#aq% Rh%Z:1e_&_q81L~mعj:2i4҇d.f{1)g G2A0 VYTbtUTU\a w J}U)pP;wz;ƎMz*󞮚:Jym0r^ t ^oزnbU<+wu:81ihq@}躏a22Cut8>ltBjs9`0:?+א-ϥm7 _)F:uK?jehŽ7&u4Rp1BM#Z)W/!Y\{2lJɽK8/cK@̲z;вSmD H&~Q%kKa0cv8ZeD^?֨yC\nr%r@K](6,rCLe57XiGX gʴMgtL4BVU2qt9@٣28#iIF#<;?6@~S1pr@?%C0ĦI4M4Љb>BYma r@Xd&66Sx] =*cx8mGeϮC\d٥; -/B0:w$9mΩMo8eT$B;fS]CF*Ujll/ot;2oq,<9ӃfȲmg ,({/$}'9CVld:8< OQ/b(v=TMY* \[KLt yH#@@fkDJbûz 0rtX"3?6> |Ea^aHe[OJf9c1T%7w{6}PDSHkA+vd y 3e֓;԰!ya\g=W7-KߤD<ḻ=\~ L<|^5̈́wu ɺsqk2rnuרEߚB|-NHe*pI9CP܉ی5گq#DcC[Br 4y $!  erm!c!5VBƃo iEVBf)*!8Z b!Bl3J㊅fE*bR+!Kʠí;!YKBlՃzC -L{Xh%$XJHu}VBc k]s!wY qbY $3OrP-$(d(CnLQnInfP`I hCf   2\p}?1IF@zs YEbջ׋7oA?t1gcHI)^%"\[(߸#VFg﯒"y P#в t)ow-8PM~PM/55׈7'&]%HKߎ5S‚율TyDDkJ'̵U瓶.TJVD~#qj2{8]ԸS)[2Y'3^<P]oǼ*b?U%ӫF,X<HXP7 U )ٙ]tHHT9M9C2)WɚM#YC"6 vAi0׸B١ғaeЦd7btr:o*'D!HB96S#ݘ٦b.ت٢hNAeD 3Ҵ.A!(krMdYn3DaY*/rݠA qB\ @IWp9|÷2+) ʟCv(]3!Tr+ Ҿ4IOJg \F`dJ^4[p22h1QR'=Q2Eܸ Bz8.ĔB}ASO~HwEF ʰUsMiܸ5Ί< )>qYX b:UtcN SDgDha}DL%xRwD+n-e;A.L<[*4; N!aIDATx} UvwtH#}  ~~ #( pdFH a Qa M YI:[o{zoW{z :ݼVսRB,IJg XbK,{Q;K B gRxgg:i{cL x9/)نWyѕ[ߌbt@v>Gϧ`rc >/]pU!Yju( Agz,<_\sqˌ={0|J!9vfCL-ô̡'M?89T T/B5iaBڈk7XY<Ց3v]  W`f]rDia5b{7NfR4y$-0An!} @tQ%!dXbʚH_L#q9+͢8RtyŻEпFP4H89bIQ13>yfx#qe WڬtFj2yr Ĕ=XjzMx.cN? -n~|@b#r//Ң0' ya֬YH\ @ƅ. LaXLiLb6K3 t$;rFS9k vQ>3ge2˘nEkhYD {b{,=̆!9cC%~~#h@W+;yzÍ1saOW\nBwidoOu}m uuu`l2{glxQ2zڀVQ}j/1묭F XZE ^lE28r?j v@O!i-4X/Nʼnǁ +uA'k@ iqC[ !Z]ҫ\z߹A=M9T@3Q>v )z/ͅg޵˧0ȷ@ͧ!O>DX0&lX#gow8b/H_`X~ glbMw}tR UrF3/en~.Ż%}㚏f `/`I?1Y.m^h̫A!w9{Q) <\dB1&gcNi RݐǛD8w7h M, й[Ƀ!%v5`&{z]F綖sS>tEYx $v?8PI#t bV œDz$Kzϩq,K9. )yN؟L\Q#l`?K06F-3ηwkrt4S+* W{[JG8YZ7srdXh<h{k,S@Y:x ӛ<4 2k0-߁P:GRٴQPj5W15՘Lb8o3TεD(VR* ]L=C%FL-QNovl 0Fu3!Z٫R 0-n:,-;.wZLhdx0֯HB[@5j4&mBs_ΰXiTOUOp9C<"j-xXޅi_L-\)@huKLߤO)ѵS苨Fv1&)lt'ݧ默f?!R>+L?T|M9ކ*Lt`kap^zVVu]y@ D[@Ѐ(w 0}_EH ~O2p+n2u+Tf$|o}"Q=ƙ6ZJªRٜ5,~:*/dEoA9BN@Xcj!Ǥ@JSu ʃl*[L%Vz2jrk }Ӱ*uc "$B* W!Tߪuӿ-̠:uj+Ӊ`66F{+9pFݳ`s\tHZ6natz6%|uyn"ɹRk\ 3sD265_\8<#y#J~%Sއy%{F'BNF@VDPl'O׬h|Yư3nғd n0|EEWoѶyL {R)I~]B rg^ДhbOS!F{ [czu4j;v[zHD !%et;fLb/ 74^_۳j. Q9n7i1/.,"eSYLKexQc^;I /77lgu U[*xU .J@iU שG6)_{vD[@R<%lqTP )+: 'n$QEx<مMRKɨj  `= wRpLE[\cv@" tיLvB23#kZVbxt>[Wl CT򤎧H{I_z[N>νבr?%fOcYKEfKh*\ewOfuN] Ei!м6sfm\\~FE"@͡ H_2+Xn}zQAƕ8X&s1?p*D7)gxoɕIgsDĽ[٤},_82J] Ugt䓲 X; LӢgB gѨq-r Y@FuKt]!;FwO=+1"k' D_ ]f*P~KDFؗ MTKT!/O79tdHԱTQyM{K~PyniZqcg";Cb a9@\-֗ht;|m 4^vKf@KL D砚AA4X' vÊhqׯ/xˈA=!G;/hx*FE/zz[-h>J<ߘ0:&oAߜ9*>9zF |CS&i(PI%^q+ <",N`sr#)s!ƺ(NFvyHiAHEkv$X|/vgdi?Sq  <Gݹa[b^$DGPhawe 0ORb) ;o8vipI/s^ޥ=SMg[+@]Q,If)hvBJ+sN E}hvh{$t 3UlJ?%eYEb?}ʆ>Y\ .2Sr2*2x>tz(ADs9{{!=q^ *8a9hi; \ﲯjm-P^y- ݿI ]8޾LkeA R$ׇs'{{#yڌb+!#-:SsÆd/ʟȻJ9!t?A%!4Q/嬔'?^0CZՀjgkx[*)]'mmiɔ(c@9*a5mHȑi2yFV5vs# ןQn:2*p=MTPBmԂWQe )s.R" +HǮ wLIǚslE\(`cq“[ŪgHK3& PGe fS״u;,:$v%0Se}XbFM$k } #7fzQ1 E:tSqڋ#D<|rwR各{+jw?h.HɌSF,h;=8Va(9o.G-XOk֜ȼ)efOQ*$x!݃=^AxURD Y6} !t+~/F^TP[v3 ^gnW89i9/ WT4X(zvx~nNrkÕ`yakh DSw9]5t3v >?ܥEgn8[*kG6ޏw]DeNmD:^!'d~RܻB ,9o>>>3fyW@h:E,mPyjKjě=V+  U|?ttMϯ\@(& BTF(0ؑ3:$"Tw/`S_XKc ka-A` zdrS୔frHeCnj ?8+@+>Ɣ*(9s|$<5vY7$J&Zv% b2@&_W佞w٬ϣ8qV ] r4~mvP=bPWB  5,؃ڞC ȈԱkC H}b8Cǡ{js^w}߀rĈD A:ZHNɫ7hW'>Z.*?a{IHGPc b)aC=t o )2.Y:Cբ4{dSSrdiQZtK-A{&ބb^pѨ$$J~OYȒqDke/ͣ+k{(z2tPq| jPGxpW(}&EBUyN#j۬J>#E mHv{o%Q -5ǩO+]jpn곬&̒+&=Tk|Z)`?RWU A֚s&\g, /XvkyiiKBވw0FT$>dyrZ }c)Y:5 3NLqV`|:b`}iQjGYo66wHb?cbE/7̈́ ˉ˦k [Ox&M؁4%MuW=U-/Hn4w#U̷΃lL$?_|˲vE? S ;j.O/qa`m$%UHjx}l=ڈ ]匦R.R52垩uo#myhSyjPxEf;t6 8{g_bBt,xRM"ׅ~Bk[Q,ʊ7mM~ܤ nSLnssd:jZNI- 00n쓅s* 7bW,D9ҹ`411vŠ{ޘn{۷@sd܄W4((pwf^^dW*{!p]L|)edr? 3Jt,b?> K,f娐(q\:`+<"RCbV̔±b#mNX: L?'grop 0&r;|[t.7o 5) v7:_ӁOk} GB{ 5nWb^Vj5o?R̐`l#dh3T<0ʓWrJ{m%A:VchJZZɳnݎ<^UK,d*H(c6&e?~89Yg-RnS >w*X=@4nNS(-ΈOSL<_666“O>RiAD`탑13- 5JbK0,et!Ӏ }o;)=+|3%[ /r osYfle3H?Oe{ҹ<9e]'R2<f?lu7X|tPZ) }fcĸv ^1>aq.3yO8tag橕Zє0- YZfIy e.5QYY}9//5) F}]@xܽK,= ]^Cs`,IdD5U2^H,=%z jg揮Yயvw=X=XbٳEK,{OdRzIENDB`assets/images/backuply-black.png000064400000101410147577535410012724 0ustar00PNG  IHDR֡sRGB, pHYs  IDATx E$QdIfNUO? FQP\pcQdAQU7@?OWTTDDYT| {}B&̝{{H2֩ޮ@7YÑ K5tmlR:.codxB16&L;hxJ4j)mw\(mD> m|M,?0@M:*.z3E^ni}siu+y #mlYӴ~;oW*%/ϭ{>3e #muDX/jvzUYG`A_.`孴L/h^ʚ6Vg\F X!`uF MtFԝք ZOE`^AF L@^&? U?p*@m_h=B;8S h.9L@fߌT & @SKL'(.;0KLAV_ :$/Ϳ"05' PKsPgՀ0b&?*k$ +9?HG0b# & @=YQnB>"0fIL@ z1f4MPz[)t\dk :&h2Yn"ڕ?oqГ0 Z%t=̓onXK<)=xz襡 0~z꧗n7> @"'SYn? @"'?MfMDBP`@ ^FMDX/ݎ@ @"%5C YnO n`@ ~W ihA P/`!9VAּ @0^ 2rP3%&+C/'0~z#| @0^ "k z  @0^ "o zA P/` y 'B70~zaA'Ynw `@ *SP?`@@@SY*%7 A @* ?&uBBM0~z7YD^CWP?`@5X.}"0Wuuaԣ/ @0^`HƯX.Q0q6e0~zz'P(WH  P/`0۬-Օ2̿%&`*Ml .`@ c^9kL;i-Q0*4P?`@2K9H̿7tP0c8 ZL`@ KG`@:gށD] XѮA @ZB֖9iB&`UimP`@ i{Ca&0փm P?`@z4ޒkT̿% L Koȏ{FJ.?70~z UY[0,16X`V|!P?`@z4`5[^Ϳ%L@c4x/j5T$yf'CCs+mm/⾛}w׿?;uֺ?33&;ų?G^ 1W O`ƒ~ATD WW] ӦM[MVAPD@ˉgޘ8 xo1&&'܈tKmNr0vr#?N+\'!ɲlҌ6, {ϓCv^s' QY!8)thplde<;XЖg1CMA,t2{i_y+e bXVi!+}q޼y+b7s>~~Od-'NBٮlM[8E >,=MSAK9̿^o X&/!"5'/By{dO j>N Im)Oݻ\ Puh \$3{OBKrާ0e\e]~ij7cF #[mDB]DgrzB:}vm<c+t˘4 kNJqIc76fmGܐǃ+C([nq@d&Poi%&We/ۄg^8^_Y-|rU2-0äU?u>u$qk&`g4$rrk[~}OX7Aߪ  @룘Sgms۩U#6r,ŀM*n/x5aja @L>[TM<S聣;M( '+߈w VrE;w4M+{DP^Uhs:`A?ofsPO =}9rF^iXRg͒Uӵrcp'GЄ VP:SM*u3Ij}W!rWgWa|XPC0{[?6 $ȖF_*3fSgj '+`']^,Ger:߫3$= B`VxY""v hhYbY*+q;y 뿱7'}dPU)QmC\>b&soRx|6=C^a}8cZ#05p ȇΝ/?&}$V!YهZYA)O¦*C0SW r5{&O|Ad3CL'ޘ7-k9Y {YѥtCY}EY&"Tk͜i:?<^]7! ؍dV6m.eJaN%î(~!pvLT̝7=n,tLKDP"r_#רT:#uy @9G*My}uws?x`vF @/ IB;,b:}f(qۺ.1](8hͺY5waejp^Uk -_/ۡ9L@-=i:t94e"I6-k濻5@0(;)z[ 9X?xPƺ镡˰fΜFq7h%}.!8`g\v9^#+;tqo/s0FaVY;h3 <9]ee -y`{5ØuzN6g$X9:v?K ioxsI+7y!b֣2Y8uס$Hʯ>p{eJ#'g Wx Ov`], ?=o19*nqvI:bm/ %e]w?O]M\WOvfUc#(NbRԯM2~R3Ct\oTj+җ^ɟu:=0;I =SslҔo]|<*d:V[|g\v=mZb亣.`:4q2f-k=c0b(;/)yZ? 7B0`څ _x"]1|u{dl;dE!Vl%Y574~~ߵE17*k=߯˭wf͚m@&%>W-*cjbfF}'tӞln/TgY@Jt$y8u^c͍3drY]Z_y!uR^l3vn~|zо>.a|ƶ"b |;& w*|7.xLmL6/G꿏E`@~0Cvr6+}|:_/K*kUMvRq?_QyAIi6d J}|/$:VkJdBڿɊѮ1Fy:zs7f:+M[M7e7O? N LkB 0mӊo*<{[YaVXD ij[6m5!}y@{Z'|< ̿C Lr㬏r6n+븽6V]eEYbc:]s]ڼ[jN3g5,۶0DߋFP(0$MPvV_R$"z)|}~PAJʗ.Jsh/_ί+<^-Kr% #X`RΫm={ YSv#<tq>Bu3Y[2~~'E]uks̯=$s,dj{v̏*'Ǒ÷ LkUpA 6-8/ӯܓ4{y`g4ݝ;=6ei6X\FM2$qlMCĐ>=Fc `g4w7\c_ׯ""*q wV1h= lίt)>LwOBUW'Ҝ'z&?9Lt3kTma|<ƭ'#-~z )t '"C~ 8>OW#Prs;A޷Jz@1%K]:b<ɩn(t|mdR=ZVϩj|i;(֟-o 1'Q r1)6&v, 3dZUk>wu̜9sf2UTGP0۝'c.iyA?L!?@0FݘTRܵ0r%ƊH[%_h_㉢S\ƿ85|4yȗkxco9-O|N̿ܧCc-6S^Qy+ᨀay3uv`ZzJn3Dڻ9m<)pB=!Ƙuͮ7f*; +I2ϓdhNU1 %0Zwڢ $?/:+y}p0F].t#-+BǪdI@J'a*<O&=ֺmr'YA=4m w Y{n[M}[d5ZJm̟P1i/I2GGm^[3p8@u?[5ۆ3`v\ng!|:8n: ίsu>--fcDz@+Oc7o^ϳ\w`uHO0U0'weF85OIq2TVIVze2V`a#%׉Cxm-%f!p1캊 Y!z۸b*hƔ{*&Mx5tfȊ^[#8soֿBq:R 0;"Y"6_$־_Zn2Kʗd_E8Ԑ v;,>}Nݽ!#Cz4@ϕۇu¿H_rDn%ۀӔVJ`P~#6tR+ϕܗjuAxt?V;3=<[\ #v;BJSxW ?g e0,)Z5|[?ƹmxr_!cwܾ$c>r Ogl$gCrGCtsnP1A2n^8mڴBcWso 3VEbuvY4mZu,`}Evu&Pw19LXp3,bA_Uy'@knoiE7kX |(ϐj Hg bCTj s}A?UhXac|r :e!kz+5`Ո4@١Fx4/#z]|d;qWgi^o^>`KvyurL _ a( C# Kyr-0D'N{ V}wڊv%k;f 鷻_4COApYMg1$1nбfϙ-xzi4&Sg^'5_ӭ܎UF(qZCrfpZLd"*>g3OU;{; 4\Ÿ큁8o{tMpK-Vr׫`5&`lUd^,бU&`^)cu`z_N6eX: )i\kXE7%"`g\[k"*5kFqӸn r^׻Uc2*.e\O^_oܹ+$.X7k0\A`sBVƏE0M0yNOO$i^q6'sX n'q3`Pg͚5#DRrUM@pR$dz=<;kE ΀'8D,LmX{m/D4AL8~ؖDμ^-Fyi\C㓘[<.OA*d~Xcp_1/?w5B_:&!+*^&2o e[ @Yst!3g\8h}3R}tQrL4VBpO>J]LwS ~/ygKr %~pnW+;c:'XAr1סCh @zqU~q?;ކb%2y5m-o~|i @p ϩnVD^%Zkh]/%T ΀74u_Bq;|ۚjI"XCc _eO0*!Q7Au<:Z.j(ǓӗݬRc ك~ߑ-UCť2Hy_6VX6UrVґ~3֋ +,߇%?&&Lw @{^%m:6AqzOjhl#&c^=cX-- s=;r!OndmR]ec&j >K׎`Tiʑ}ioe#zp"@L!Wwug3M6HR (%sk*e\Ij `B -_03p]x0.BWUdkg[RCPn5}Z}RnV[RxU 02,[Eʜo,O:5[`g\>"pmǽ}}gnX{j|7I愎(3u^^YLe E^GP?{Y BԷ;w-B{KSVcݺr5}@ؓa9W+ț AG6;^5T300swz0e,D ΀"\նcc| 3n@䘝[/6mjWm~EihBAcັw0~% zqUEU, @;%1jo*44]/-/2K?bVX>SLm}C)ٯ4?pl^m~i!wt(e brsP1\)/3Uȓc"HC?Br{\Ibթ'u1~hhb ZG#lujW5fhqUt"w t,[3tl#iK\朶c)UhV[grKU1dً/~Ȁ cK K3q^0gI2'i͛RձpZSƓUQ]AB `"{|$뭊UInUo: >YE~vr$g;q}1fj  ΀8B 18'" ѫonٸd=w锕?/[#@C @%ۀw ]_D WL @$x,!r?׎U(۵uWor-1\!*OM=Ճ cͶ $i"!>  9}O+z;/^Su :.5>zo/+u#@C @?|JZ%[¼&q7jBMKn1 r^'Ho*|x"1_l9MIwE8eݚ`g P2D.d4cQȑjy`BP pu*M5Y ^Ě:}3`QzY)o?01^3fnΝ;we/u7wZP.[q[WX> 6ELp--~W-Lsn7eEG J3Eg*nٶL  =XK5#t\2} mSxv͞ÌqhX̾eQwPnx'כ,_m=av "P `8sZWn/iR养D0i+\2t @c7㪊ӧ<SN]cd˭yjgk_1.Unr^ϪƸ,n^zSqK0([(+>vjt#Z%Ο!kB:Si|L  =W0;ݫ\OH25MN+kҙ)Ξ= &P}+?<:hnȖWpRKk1b[Sީh̫y[պiUO-+X r9:ϵ0l=e&st(؁{^V׾|LDJ3h1qn#ɚ[&L  =W7 ]}a /۷]23CtyI-uƸݓ$ys 0BǟX%3yڗp.rv ΀8Z/ . 0!.Lw/ιAR&J1}Xulʹ-&P}F'+;f*n%S7pR$>W=id^9u76nKʷ$w1Xs沊\Cʦ3mܷ'D^`gT;oi`W.A^Uˑ;k{t/o>.sT&D{V$2T_т؉:$t\Uvg>bV=xJl N|ڳ[} @A7 ,/Mr ;׹cHfo sa;|Z+f6eQ^*g$G^2L7 @No8 h/%e܇tFZUʍ` W0!h`vs:tVȳ9P"?ZޥQ9k sRͫ @OՌ'{zhYTh.b *$O Bc=&3 3N~5,D,!X_ %`qHy{ e;NyChlv)ϼcG[ji\D0Y+4Z0;<6t\Ucu߿rR'}rY 0}T9&*2&1](IxM_kҗ\|űjy0;CӬK 4w K8֍]AD[LwEtmNn5h<Һ둘5.",C @-.m踪''D`&վ@Cr\=`.cePy}VwP.ن͘A+)6w1nGZ%0;`rHXB QWDnΙ8/ZvrI7JMmG.βlGex!2T_тgB@2RP ;y[:BF,J%}4 <1|O8Tͳ @dxfV9SZu§yv9s`-"{Z@6c0Wn !b AazrN$30K:Tϱ?rMO5+HnGTK"2T_тw @ž䎔{AkzΨ0mMT~ pa5c0d/K{Cq2s ӦM[$cR#NNjy0; }*D,>%;M,~Cs0M?K^g%˪TmVGTD0Y+4Z0;]_H뼐%>CQF6GN?g)V|84/vGmG.zt\Y+$2T_т h`*'o`{Bvjq۔ӹ B4Ɔa&jmsu'@n1UF|r]dXFtʄnmi$wc]\"꿻VXwsi !&P}F `ǃwl= ox.n6mNgo=OTtDy8]=*6[9l mܘ\.y8[$DgyLCjys}8nuR$lq3T @cfйWpogLvρY_$@v_4'>wΤ` W0!h`v>z'!;7sxR?O(}eL64F"NYPod8[u,Yjf,&mχUROj2c7n:.p8k 5=(DIoK !⩒hm"i8 r4Zp0cK gds3˲5&L   < ;`٫'<оc];x/) jd_ٵlxmfApBkw:>_ ¶5cƌ~夊'in(#}B \bczA W$.2w3+ʺ'g3kw|LklqZXVj3wȱݩ]Ohl$zgZ{z?UI\BXw;a` S0!h`Nd@s8aF rݔ(eJ2 Q׼s KI-ܯ؈nK΢$B^jUW% 3TOт8A=/ gBkv%k׌'y+ϝ n-ϴ͆;<_eٚUpp[|y:)<tM)ѡp;Lz   qxm:Fmy OU#ռ͇x V9Ad(>KʫV9MD:<؟wB6,V]DmsGȥt!M1󷕭ʪm81CǦAq/X ZJ 6d".3N-b|s.~־[˟>* w7$nV,#I3T?тḊ`c{B5J*Eڰ3答>Bp q]PjeO*.MYx֘)p;`S @AVHbiwiHF1V:SI Ƹy6;CĹH|ֶ!Upl8oH e>KGydg=NE0i'4Z0`ٛS ٢<.Eb N{b(0Ip=vrô!{1Y ($fOɪm(̘1}ձ\~k1.M8G]uyбi8}M6Y $yM| [tSOp;\8`+D"ܢ$ ϶<َ*j49 @hbAb}(d!ycO=}ll}$10iSΓ=H Q@Un ߦeƟ?%`l]#Mֺ|]448IɞEw Ov!r'i8 [EKbCt~|2(M;xLwFSc#/ٹs9`>%Cm}>ګ'[E0q%4Z0ٳEx&Le'fyHkY!߽5[ijx/ӎν"dl]@>U}$`89cvOm0eb}bqƼyV _7pC]ҿ"5ӡSf%Fr*bmMrJpW- 8IH{U.C9;:d>-N` K0!h`v/Y!n+'$suVȲL9&O YQ lo{6IPmץJ>DP9so |8LO4];tpg{(5򂋟O$I(5ܵ׼X`8rM X/ǽtu\i: @-a0x3S9ox)l-Pw)fpg?"[qW} 9u~;??L G[<@9?ۍa{*WA8{\1j u׵cH2ͺ2/Il0s^ t-L V :Pޚ]ɓMB<8?#xڴ9^MYPn.^ [4|g{qo?WRN[gC׻$)g:XYbdh7[?WSzjr࡭iz^u%>eE۲4j @Ӝd3_q`Ӫc8>eUʣ mS˕re%g{urg>Al37͇$KV$ݮ' w@f[+#x=6|Qn6r۪1fq,  XIvĘv/o @OVr=Γ 4s4}yp[9K;tT~Ƭ0qA%<٥'GVl"YSN1,\o#ng{`?ur7QPa'S"@C @z.`,tI`:~0b,NaRHWKq9{e@TE Ek TcKnI&6IժyC gr9h /v/R>6U\#0Y :w?rX.zrp̷sXzU|! @46fH$Ѿ J2zsSƄΓq/\2tM%s'3f{@bXbEcחXl]dl?UVbrnfA[Ȋ$ߤ}^ $W;0I*W " ݙqT -:* ެ}*x.녲* x|+*.ξLwWGfDU~z̈Ȉxϟ&b z\&UPќyZ3'oim_1HTi)} Z6/wSF4ع[-$hU{mF▲\`s?/n.kY/}1΋2Voxru8ٖ>B9 м2|28aO{Ns/E5JW@eR0A @L/k RjDgDdqKf͚ݍֿSUX}DoŊ5|}L&RG5t̥y`nwŻ-W#tqR-eLn+;њ*k0w)lX. X$]_[ԋ~/ ѓTcO+Dbs}75^Icl cSaM==i>j:`\_1m/<{es'{?Riݶ\Su%}ȓ[6,ku|.-%k]63da~QPq^VkX_?.cܛKI۠n/Ɲ#i\]J (c_m1i"kG;&F RgnM2=$콾4+߿eoE4S?D"+tˀD08\MM+G+B'{hs!bfU/z2^͛ea3FYY^>zl_ɽ?Mn|Ѣ5e1YhF˚rwm'oC3>ȡUkrG6$l>.kl-}YDFia[.o\ǵ7ajNⷮh׶~~jjH/_ڟ}˜G L6;?Az]׷ۣ pﵵm1\k_!B,L͙2nfnQX 6 C9vfk/@WUNϽ v"[l?NGn!5JS (|MDz[{ƉL_U [# tNR˭EbVۄskW-\TGM2^mת\xk5-kESzB+QV jx:r*jZu:تzƲ&r8 ʺwU6ZU\q2ױ.M]) Tʢ2l1k8{y}*2N!4 `_˾%ŧ(Y>5JSf@ :{,Mv{*.H>6f<7VvX>m3㟟x˷d1yikɭFSn u6 z4+. (u^,kunkޚP7yLEڃz+'-39=[slӨ3_y/+6ym{8;b\ѱuݬER\ †:yu;nx(= xge cΊ^J`7L5?j'FƟ6*x´J<Bs6)nZYws[c/MTD7i+W|`vګ,Su|齿GS4 זܣFqϽ?l[hg/e˹NgߦlG}(r~)½b\4$nY{Ɩ).& [W.b!sҖF1Vic&!GzGVcYhlRv9njp"|cV|_X. fy,ʃZ0҅j|2яZbEB>X~ƘC<\j^Risn u-wB-.=%ۍzQC ˙j圝1Z&mJ+/Yb!Rٱ&)'án[~9^DLmjkXP>UFLNN$q4f*Eac>X̿WTPJ -g@|]g=dYY=GAӺ?79[m16s$L@.qzw b AݎҎfbkpe! ,B(&wVjm-iL~MS㧧5L_>2t7D?5IK\;ТLiTfffv{ﲲc3wRI_d)[әs{B]FG|4GW~vnl^V_{K;pcxuȶeV"pLhz.-2MXkA$F=.7L;/Mc,=+􆝄y2b͝.\an v\ZҬYsr~ecjsUWJ+B {`xk2oܧ6UtQ)ficA -%_Cdʘ3q (r9^av|Ho}ZQ'FRoM1iʼ|snޟB[~no[aL>ejZ$˜s>5m?H{:I fݿVU.{urIMe}cjlX snF5i$Z#W\ź͹5s/ 0d?b k=tK!K{,,L޿UEB߿HrnѼFH3zYD :WCgLuj8Y`\+P/ء4㙀1 "P9_\ZyjѶYj;FQ=m_7PъZH;܃̹/q<_i5[#fVqyMh}&iCg=a3~I`AhѠcuyT w!K#ib( |?w:Dr3ػ[1wtaSmKхsoϽ? der80 tV̝{_ ܶ2Oϩ\GONNn @>2OTm9",>7==}Ocܛ],^ǶY`l^hTU4G+i~L9u\ ܱX[Y95B<0E8B5-ێ=@&~OP7c[}V^'ƴ?Gn3F}wsDݺf˒ =#0HYzz¬[:Eo9:NR8#}A}- 2s79G;? 7Ăde`V8/-xZHҸsMt[5ERgl7n+[e۾P+I^-Mˠ段5ks A}o_5%r}Lu\murwL u+2~tj;ӧQ$(G.Mr:Z4d~1,2?K& ]Յe/htv{l'ۧX L{Oc.݊0j@ǟ:I.5Ê2c'a[ɩTCLAS%~NF ՜I#~ncq4_Sه;˹xs\\-aMmH9S"@ѝ/@C?cFjC&jV/TCI7>Ck:z3=0OUmѬMNm!_5oiyz i``1}Ylo?-@u>TD} ͳR xcvT}N[OM`pb"__쎸7"4K'_}+WLT{- u|YMѷ6|_~T eq {]0QѨuEAc MUc\yOP}uʿ[|rgے"2۵>eVm1677 5n+WU.FY̻r;RsK?{җ"}KFʿNՇJĦhT(k d -YG_T(?Qֿ_҇?iSwR3 _]DWmIUEx 󢻃l4F(sAؼtKp!T΋=B=REmYjhnR}2^F#WΊhoq |胥!a*YQjQQN?}G#s5jŊ@4+^Mf{UꍻdGh5?V/V RTh3bL Q҆h鵣^XF#0ϱH9E>t1B)K> s1)z_(oF<-b"Kv1 yG1Lq_ǔ]L@ӱT]0CӀX.dm^_3q%K/&}X#Bth* `,sJ{5HCySr!tK%!>{8϶_"ׯ!q ]#ҖFM+z`,+gv3Ο#m Ÿ;> JS_=F`֟ 0m̬}n)}\K!=6l  Ȳkϗv~[.wέ}\O!DXιSC>E4WJh@ow˱a@(0Zuv[m~b'A4t˱ @uCMm4νfjjj"q aT,=(QUc)HkEҾ]+EaYf*>w,qkߦ6N󌉉 I",!аM}n@`BXֈo6iEs՝Τ| [;"jbuZoRR'uPZ5:k[:31w, @HT @~vwma@`BhV~]mtI6,l::)ھ dyX2IDATx]`TU>[&=!Z KU:kYWuײe׺}DAi {o{sߛdf2o2dwWfۡ ʪA.%~ %ECQ(Q(6:*jR"BQ-|= bABd(r@d*Pp J()(ZV @yD|H (QG{up\Z:B'3DQI,KE )hKd~1! tG&u DDOR,d6*1! `'(+8dQ(+6{R;_ *|A ! _Ԃ!%<(}}3r(Sx Nb(/([P *1Bٍ ~F-AY2iI (9A|TUB^^(ۆr+'oGBx.B_K)ye2T&,6ۃQ^BI1XW\2LtgATyOd1 (r(wa7&kX=f޺}~ɝޱ}3Hhq >, &W)Ε俸؞&(MߧsL Δ~~= j&9Iin1ķ)ݖ@Z +,N1¨; Do@ެv[״nw7[=#?^aݧVcSFt2bgz\, N[f1r hӹG.*  Ho@QX^,xnd1}9Z qm۳}½G~[:5H|vژ)£T"9rI1=KeU}Rv0^_:=aA$W)1w-l]m4Apã_;=b B HjRm%JFHs%y*Z9gȭd_[6b7'޷eDjD{>IrH }s}[&{@VT ﭞ m¿_!ۨUX=>.<=WF[~ydЄ70lcm-K jM? qF g.K~ OOצڅ0- Y)EuC/w.OTzc׾fef'jI[>-b .ɜk+@QUKJA6yiIߘpIɥs\{NL$Tv'Pc17,,`z`dme[a/"vWMLwgΧO*4t8{KT۝upO0!k׫4PXY& :ԠzUrkfoНT@լ tsÃ@Pdfl_KdkUb `с-pi:ܑeQ8 gKoqnqUl*"0 Bf]I=X20 dvcu *z⒀ ]Zo5>&B)qdf֎q #R9D~oMjH)AD|E Sj$dܽ`UT0H\ce ;$]32c1%Ta+1Gi|wռIEUV2>9({/M1_ԔXEyү3aϹR(vQݎ_/1&*vBE.qɣ0x_n'%;wacZ_idN-$ơN#;aөCbvJ cB~QUYy_u@~`4RDk_@|KƒF$ίAY)r(/8v`D 5OVdk kY;CA-IvIK-/>tcᨠȂfiD$YRmWB~2?ssk_A5[#Ry6֝Yf&s-B@![0ɂ rչ=#PQ3%CTҡ+FFKaDXm6IȒܨ [johU™RI? mUR3=<)8 Y٬0 cPn-c$@1=>g![m[T <&5r)DRHӅN¹0[p xh}jM&ձ+zb!!2ʥ_{z *g] d-(B2q1aH8*EéMtT"5⣍WGl0E ԔAeY ?fӱ弎4(TA>ؔ,΃s[^6 7Cp';A-.n<\ӵWF^zF a" !:4[DClHD%$׏ =]|Tעgm7su]G:xQh—lS~(}IU%TjX*cYQRJQ&FÔCX+'p9xou R,Utlf`R :#N1^4!Z=iu,i%]y_̉_6flʆ'O$_>{/'#u^;6P / jJAў!j؃!Qш&9h+u$UPks| t8;WV{> Ut9!%H_ٳւBD8}u4]2*#8ٳwAe j4,>5&u(Ŋj!{^҆b"s8X[Ltך_`"kč? S ذpI?s`Jg&m^ Og, NNdfe3{bQ6#F.e+\2hYԡ`k΍7ÞOO=ӑJmI'AXIVz0^NL8 u'@Em (ͱ b]HHfo} t_7G OtiM&c{ByС`Ͱ>*-f+6 | Bӌj#O Kb:4 t^m}N rdx|8X~h/,ڿfg P13qIAĵ*# itC;Cx篟~: k\9,EEhQ_!.X@vRA0}'c0OgOÚcaɣp0*M@7RJy8jv<'ZeXH5>"z&A;Bv^Ka Gs`˩q8 lLm^)6i^.Z$HW ~ArBXqbP6ͨѲpZlkFpo j d5$$3yh((,/C`3h].@aU%EBأe~с>πXkĬOVvhXV.!]ՠѰERxt.kMBC1 lsp[UR]%fbqPH#gu>>Rq9Bƃ+`gK l NJB=Cd" A ɐɑ6$"15FÙBOR.,b,̇yp_/fSm`mh uU9l"%QC^%HRqP6٩ LMpk7@8!b$8-bg_j!IG!h}K*ʡmJpe5pIifZ$>TzU$^#e <^t,y}dALleCV3ivlAx/`NY"Zծ;j}T-$z bk[&xт@] Q"ǔ2Zx 70q4м6 !(SXr@Pu}tS r3Q}#+$<ǐcJ%-R.w r'4!^d6q j1h2@W 2P98\%oi$C;K* c@H+$.&J@(]g@ ̶3FBi"\*M@H ֟:B ^,v( ׋|\ 2j h=t$0&ܞm*vF9p$%°Ƃ;w9##0>F(+Lj#A] J>F~W"upp\ pp0CQ_hX#ScCEwGʷENFC9א M YI V0 DXP3ć*}FHxQSОbdpP&kE} -988zAlvHcQRkH1,63zD"A-xߛjZf3Zl(6 MV[,#b Gp~ppbF끌 Cѐ 6ŲX˯MX / bEzjet6{[m|H88.f b\nۍ|888Xf)P)!=r˭ Ch2h!*zQcl앖SC+` Yqރ0= vF.BAC!˩ch\L88. <888.5%V>$D:>LC.jXpp\#J.c0R.Ea' RƇb /P*b>hGEMӚ}\V.D./9D[(JJP40B 7'SF-âqP*(Ln=7dkP4yXE  G(9 T)U($W+A2I/88У2#A.#0W+gppX!DV.G=# R2ppF!^D >1rC8G54+w mg]kf>a &9"qku#A%4j TJ%8ũNG1W`qj25R\3#N>QPFru"V~ lj˜C^ՒTE֣r#OW#9 xS6}EKx~ J ~ Dg2R/ =8rG b)Mٜpr9-.he ܲB95+Z"%/* *() Ÿ=[Rf w{iI)l9UJ a3Q=K'-CP#!ex6Bo U".Kz\ĆD8 TaJ!yAeXS@/2y߄> v'8:=!A6I} ˑŬCc#:u4J@$/ߺ 'LSwV+DCIEuNjA3T qhAZsQ&GW8^p0Sӊ6)KHt*R'!G$DƠQ)a׹P]WgP ǖǠ-W㦋Q Az|)RC8:@}iCұ>P:a=0h62֩96AȂIA.,M\b%Ԓݫ<؍&eqB 68$X|yu) pV tGW+ةKVúkRx(uҽ"%.t*5*[6[,A=mZ V/wQk2lNֺ<dr]!2OYbQ3ZNNWjP-`ݽ-yNwh=¢!=?\QQ_urL򨛅7PR]I] 8x/ =Jjv( xB;3s*8'+)4:|fvP\*A3d5CjtkHM:'IE>Y jY?;61U:Sz]jVD1h$cXQb'x4X'st^Nr + cU3EU9-Lփ*=ڣ2$Q3& 2ZL)xΚMF_m^'rLS|*+vvt*s\CgCSBWF7K7=>] 4Zxc|b뉣0{f:\' lIK W}J5(=}uf hH83ΏPU[ /2;Y#ycׁ,+AkY A>ŠPM dFA$׊Ep:/oRG޹Ur9Vs?} "'g@z&M֒wAg Rʛ: CsӻEWkә `">}lE?-֘`Pt^*A&;`E2c3!?Ը0 }i,ݿD} ȫ*Y+jc7^CYKUoîtoKqŴ QNQ=ECFn6>1(ߌVӜu/) i"Ƈ=RYY~#Ajڱ?(G0cj^)0&7s$M\%W B>4)gBߥ/T;;9yXuxl!4rL]yEumRZI\-4 bFVfi?3iۼlXeajmdT-%5*vwOb]-=ZGgY-W DFk'>S/# *tѝ{A4#pL.Av|%Mnbr)vg-s$[ _[gSR3ȩ(֌;{d-j<4SeΡ_7) &թU_AFy=?k 9N +''9<BtAlS44axSBr$݀Lv?jqm]P֏L[#}#S8V9K|mxphRRc̵Z"!| WY\yb؍j2 T́m3Y;QkN=;j_SD y $hƇD_݄dQ;mi=IrVHk .ý?n ]kۧ=_t|8D"@o$}"Y`u] ? Pݢ]X)&H98VperP]+1,>Vʗ0(-{Ed=,~BƮX/=n)_o 0J,7QT8| ݪαRfP7Kp IP`G`dGI$:s,L GkYRGrhUt&ARlt]  (I,ː$qERaZ# ߱3JK 3ѭ6^%IaR1 tQJPH߱i}X ߘb*Do//eo 1掺K1~b/5yR\ rdI2x"u ԘL.%uj5|q9UJXb߻Vf2:(\&J*|q|<;ؕ}ݙ% O_7!(a!-^#>$&+= -k(*ˑܳY*P = e^\.& k3?m.?}9XfrPwR 4f 2IKdFȍ1`vq>X` +hthG^C:thzt?EҪJxOA  d_󏑓Y"e%b#&4m6l,ƇJIkY9ٛi5Ʒ.PeJ[ 6gag+[\F W%GH)]p+:ĄߤWvY۽+!ׅ,(k2JM"EHxuT.Mᢟ1馕0ao)$FZw~[/..7]*Tі"+ܲ2YUCZZ. MEH%!>=1-$7\.Z9w7V.-Է#?Z rOąF+tFvghzrkW5ɽp$hIZ#5I(sFÓSnR+ת0a3>n5@\rU>C '9^/tIdn1GqJLv $e-~쓋4IrʮHFİf܁#ߕ !||1&&#M=xwFjJ5nύAR|xQ@En+60AiU[ڥˡͪc{z23uGc0]J_aMԢJ8'7#fʼnQؘq"!1"ىQ^]-^Z2rKYϙϕԪ>kiZXjl :3TH2_AH@ 6G Ikrb|k%ջ^#Ү'ts3jQ5)/wA~Y '9uXv`7<7vt{-JKo^? ŷcVf6{թUR"+$5ML"盖PPYvvj YUg"!5}6d527r;,63}Xac>jE둽+ +Kje^YREVɂGw~HTT@ A~F{yjr:)kYafzw.#uZՇ'@=TnX|xhvJD?FIBL f}}00r Af̀V)޻gkpHE.T0m)n0 B.AG2>t ~϶4Kwӧ+A5Nb5)r;)>ޞx?oәY " KFN>U2%N\:@G=G,Ji=A D70WS }o'|n5=C;_CFfe]}RDϞf vgҪtUH[g|+} l!!*sjGݽ^Po0TWUI5 !,0]~-|k-VWM^Y:: &vr|B?[ 5h1x)uV3[DTrD+b%ݩ**UrT0'C3}֭gAl@vraLBM߶ee;.祖(Qo`MؐQpm.n1k i{NMY`7$"{aPߐ [-:0 DkEԫc:=yǞz)z9\BϏ hM +9jnJI) vg+ka0sz,)dèmZA".. {a[F^[rT:򒆶:\%_0EjBy]0p-3.˺uL6W0{5HKaɃ5wk ' ) j1t=tOn2Svn[ 8LP4l92I)uKΖ:C5 yI|xР^1DTCS ʫT^d . F]9gCNnNh9"5#Lj1ȽPo<=6> w<4e|;Bvi19פBŚYtCي?3V.xω!`@~SU+! me)=qtSR󄮟5Up&Xzd':7qبm=B k3-wR*+J칡ЪÍ/b0ߟ|I+&KG\R\h?d->J9NM bJ˶{8Cf|WDϛt/yeUO-h!4O.8Wb*v -#w*1 b;@&e byl= ĽڏijDi_^0Z{ ؑ=Xϟ6¾S Gj=.#&76sogDY!X1Pb,nqY?WO))_TwrtJAKP% QX[EdZic;:Nܢ 4+Ru0wթUF^*=-6ip瘤hYz$oz6ƺD6:t1Ե1)kV5jA-,prJKm07T} JHTj_68eQt[kg>NءPQD2.GERW:/glcŸw)?;~e_gԤMdᚷ$NBc+~O G E R].]TU8PJ)F HZ9s,s)^2I $_Pe[bIENDB`assets/images/twitter.png000064400000001636147577535410011553 0ustar00PNG  IHDR szz pHYs  PIDATXVKa߷ -tg4 z(*3"aCeo] MeWuv׽{:gtչ|ͮz7ws񸔚g {%YوafHWa~o@:=ǭ]TÇp$+2k/W ,vF|u5IȬ̓?;'1J8"+1VoHt̎ _-2[pʓWVlT_ۄ=bp;"ZgV3WkMd|OB6k},_F33Bsa,{ L,fBozhWJ2r%촂SSxΓ<ӖGښ 댹J 75 Ymp.A ҃?s`8_IhM@3Hڲճ2Xq0Zb7ĐXaj yQ;0jv&tZ@5,X<;KAA&EdN{.",hTIj2ӥZpqe |-D؃8 d_8!Wh7ѢŚp 8otι'L.vġ?6ѧ$ Da~#CFL:ӭr%zм4>Bg.Zԗ{8ۯ\پJK6^۽tbjX8` ^Zu; mhVD۫#DՎ69wP-8mjb-u&(_΅r6lBIENDB`assets/images/pagelayer_product.png000064400000006434147577535410013563 0ustar00PNG  IHDR@< ٓsRGB, pHYs  7PLTEUqfm|aY^tRNS .Ngy]?-rMfIp&Tݬ 48 p@i(ϯ P`Q*2b֊+z0E߈%e0`D"\O[k tPu6 ܐ{3Xdz}!=$o1m/VaBRH)zxx IDATxLJ D]d" *R҇ګfaP;& M4ѐx]oU әVUWzjhТ->fT?*|TDNXZOM\5V֌ڲo֞C >zB:myLc3^65jX7v{Yg̱9./M6tLǵ˟ԖFmXj[{7> 3ըZdW~)pV'.w~ ,[q=?=N7]vKH3P߸cpR.29ӹ횬` [=>篸RA4m٦OٹnI 3$h7Lp|^Gwe)zxoӸY7DL7ٗ S1lv78[oQʬ9 !HXF_ O%)bQlq8{&se_rw\`#4(vȴtf~xX&?-`t (IkhjdU64-D fhFZ.˪jߏc;Wjpm8UNbnyDZ;(kg(d~"u.)G x8MHd~bߍՄiwa72A;Dⅿ)m)== _ 0Hցs5lHMa ^~%s~˄?܍`@A |vm`-mGP›ҬrG[!%[ oK).ш c2w-`./d1},c35yMK@so6FQVt2t4L\uK ~i92ζ2Ds#6u&#kE8("(ʴa?Er=`LInA.Q4r[؅XLGeレ D"s|n &w.[mV4P&q#>htܡTL=-@@,,Vm0M:4E]0~#mÚpPab( d> '"H=_? 98Cfmz^-c5|JB&o> ReoF\.w`xM e3`BS.\RfErcq#»rMX@IhŤp@QHVa092EZyCs:\-@)A.(OJ۟Wԯ IeK X*%R Bz ?s"^X-)rZP.s7F:1tbl=,KJSF eavCE)RՖ#1̴eeȅ^ɭnd_|,/k|$0 =y=9tBL`ra{eīe 8tV$he:á 6:I]H WGd$^b@g 8@@@MXʳŎ(Ѡu::WP٬qf8)GlЩgHr!.xKa|_~vF,%?aBe @/{؄:r̭^yKqL:53ЬL L-zS\26qL{ՄrQɄHKxX^~6AlD"\TtF wnykoV Ð6[ ||lk-&hriE:|<뎗c pBë)vGqBbc;ϊnc;'RyhLݵ~ˑ[fpHo嗆zQ3XqЇaV{ pgC^q)6uQ 0InODo袼tu"/TW@6ƍ80 30{q2.\zeH{8dg]=Y;fGi+\]m} +upǮ?i DqFtJIENDB`assets/images/backuply-square.png000064400000125762147577535410013170 0ustar00PNG  IHDRߊsRGB, pHYs  IDATxtuk[nױ,;^z87W;zd*A`WEɎ:}8ϲ-//" ^$$X@o|`˲2޿93GD"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"_%4lD*;(l^'DVf[g+D"Ѱ ,H)ډE%xJ ׋mbI -|!\KQ_[NH$D $@-tnrbBñdJ(݋e}뱿^~x~,z')oB@1@1:Dk?D"H 8ց.:>nhCjfa+Y\^7^? .#,ȧ;w|߁8k(Dq(iݔ];}7y{ۀ[U٤fW6U\1geKu{ߐg͘>mt{p7!iX$DcPУi4zK~=mx\Yu\]uB]}D+Ǽ50Z]}qo_O ]g|-}>pSD"(TrO26 0*: p{m ]^{Z=zfY[SRGa{x]p^/^?O!oyW'#z;ܷNrg~iD"h(xwaJqc`ӐZ9F ν g+E.u8AK#h?X>]= EWBY/z>cOυ='~^xnf9L+psfd= pO J7$nF:H$M.U T*N-0 Zn}/qcN{I$D(p#E&5NI-޳-.q8[&>#Q2xPhT U~x;(z<^g'~Uy~ů3s ?7z~sVIuxih=h]E.u=)ps(}[BNIٵ8-H$RK-{иVv'9qnx?^7_w]>Fg{n?gyLXx{ϯý4c'k/ еo;1oU*ioD"э]/J?!n>?m ;g\fxm~a7}ĭ _S yŻn:x7Os 6Gr2!g/s~=i߉D"hNR^~7u7>q3N!ct{n7 @Eۣ0^wu}ݽ[p?wν(m$o ;vDNQ$DC}Qggo1g'n[#t}n: o~{G_|.{=߽n|›0ϑE:q 9p|VqYH$@W> q[8_l;{ف pdp^狮NսwI]Y{/w}{>D"QBbEy>ݸ5R!݅10 ^W:;]gVY{q9gw}J{e\VWH${ {E;;EC* _ rq[7:ĽGuޣ:^#zw <8swߖ] \æ=vtI~ `_]#wD"'b]}xO7g!W>P4p>?XX}vv='̲`w]ۮ^괗E"h)qJJRKVv ӑϤ"Ng%/mPg o^<8lzz:z=gvZg)555'Zح3vxFQ{hH|sދˮE"h A{MyX@! 'a97- 6o kZ`]umYGf8Բ>&7z Sߋͪ~iD"јϡ;Ϩ8y]\ p`ݳk`jg$BN`߫u}n=>>VepMu:D"hT+t|tU论] <Xw׮ >~N]cO~Ā}{1|w|&p:D"h ]7PdD&ffO;kh}L|C#n[?M#1¶~X8lkLkNP<c+:`_סϨ9cncn籧_Jn=z.]qujH$ +OA)0-I. mUhs̭u8 o{7gi÷k!!my ,m i#9s1} z bt ٺϮ5&;vyW%H$rD){>b Zt\Dgwrפog] K ܭ vرq $wPRwG\zr>[<-un]e,DWjiieOϨh1s 9(s5C}Yӗy(}˜T)xujEĸXq?0oW7cxw1.?kUM?劾ȃ'=N'uF\L([-+z$$_]-Qx> 3؈bc:g3*[TZE~X%PI{Ubnt;T<f_fӹ*D}a^vm+'Su^~o袹 r ;=u.y *mTg>WF6\sA@=N"(T_H-=+:K0?wZ͡^ۡa Nk(]fyӜYRGGBb@}ɛ4="~wW͹x;~)5QQ)%UrSm:u;/șH$ %x4iMfhQ٫hOZǔϚou܀;s?/?oN%Ɣ6Q +A} 'cꥀz>_P?tD"GRJ?IhdV?Yy%dFܜۺv߱A6oD-[4/464#'VAPiz@}%:>9-ƙȏsa~ǎsЩ[wǑv?OKW.16lIzZP3uq:E"8SJO=m/Z0K=\'z.@D#'|"`|0ںvQ\%T piP?P:NH$̧o?fn]~r}۠W.C}wyӹ.ƨ~<)eG%☚a`>~``>0GA1{aN_qS}'Q30Y Qڿm}߿ԟy\@}>Pef.%IL=PA=fyD/y8:sǝ}H4 0_Qia\b̆ ؿ[] uSwC̀P%g_7EgVDcHyPJw*g:sOK0~g Wwu)m"hxgV45W;o{\@.1fn~Z;\P?߉1kH$J-oʞ0WUsWWGKoo"w.QHH0]G|w-΋g___Z@}5P@P/حm*6@=Uӵ@$R%z iuڊC3ZU e7_ * P_^wPP FPXfyZEᙕ's 1x{NGtH$%J.>Ŕ# ig]k \@K`#4 |@=wzztfG2>D\z"\h?fF4~ g74PP1 q&:/9GԟԟAC5Pܚ\j1 'Pz|.lU1z+etDĢ+Rʛ8fTB3o_җ਻00V`.!G _eYws̀azB7Գ%9@rTf/iN HJ.9W)eMMi+Zs} n.8TxiKH wUx#Mj7gw@]E-^tDԊc(4Pp.kv]|is _1D&J|I.6g7<=PtHVr鑤Ԋc[*Ov\yFZӦ_67̹\BborNQni_6Podž,OhZ†@}uGNH J.=Zk%s%$<P?ß2I}_;Y+Dq,?\r)MծKpw N`.!1WzBSCoKrw}Iye/;]WD"J,>8%Ԋi(gQDjzяWռ軨Qlw" A!Υ @}7Cdo7LzH$aaMRW`63a .2nӘP`.!q0o0iCMS2F[CUң*J(ثvKoUGFflkH$򳰳vN#iOcYys|}'wxm/ %$=pNYeP3]Ϩ:hR~L%QET|G3z.`G#3>&eagvNܼ}nsKH#|A&O4?OףFng]wKwވD"? ;Ug_.$$F,L^ <Pף$Gv\;} NH'%z()>x3}Dvߑss F_7}VkNe{Ȍ9]wD"Tzٔ;3ӟIuQ\hGg߰GBB/ar ƹ{r^w]y5cUJ:RVPe*GU\~#t=*+rYO;"hTr$3ضTӰßl  s=jb"0uӥc=N@P?R+O*o=:{BY{2rY?EdlN H4LJ.k%%Z).ӧ@E:yt u;C_Ġ֥>@}+؈_ 9mS+Od^zD%[y;ULN-zB~52z5H$ݣO*9RWUfNѷ]S^EwMT@9`Hd uK X7\Ws_EMUF~B%6K? FCd/YV{-2&!"}ӧG"$VrYԊ*Ϩڹy.R/|Cڈ{wPG{o/|s*u)™$™]*&{;**Nڣ;]D"=(iRJ2Vb@EvT$v՝ 5zڷzdžQ5_8CP 8A}+ؘ_ zZZqR%/I( ocz@y"vIeu洓Ϣt1̯_0_Ev,9o+sEYkuߡG_Cz͞@=P#mYfA5|@"4 5GEK@wozZ(z'8b=W`aN̄x)ǹ8 L;>ae`~KÜm+n@NՒf{Ċo_M7C}"P%>K'xv黽tZ. UHG3X8܉ $).ܕ:V,Tb͓&s |Ba`e;?a ~~;S7K'oү׼. H4j  F^#tDx^~q*蔬T-sW治8s u4o4(~/?}95^]v4z/(:!yCq=.$~{'inx!n.81m:uKa /9u\ hl Ti?K}~/saA@}Tg>utȗ(G~;&mڷc :8R7U|b$:ɧT2VQD7QpS3OW_n|umS*ToCSb ~e VH8c-u5ue`] KkN>H['yGæ0]9|KEe@n؈8ϙ?o#/R:>hxs,P}AujKjnyQ3jK KII(sv :6oo[l~$ ]Lċ1 ^(݂Wj3W6_cA?_wvzl2h) o[#H珵ֆi6֋֏ד`  (+6:i̭Xwa;=7W0P ~|% ,OPfgT?P>:5)]z^=B.=]zzM]M-qH4fbrN;ޝH[\ ?_\CVp{])Sx _0ߡ7P1AIJG3>v7>Ɵ1}@cy 55ܥ.5hDGѥ/.rj᭖"Q 42&oO@^ /<Ս"\Ul܊9 Rl$ۀ5{U>w>.ںӥ'DLvagnhAv+%]zw[Z:'lɇ_NoD/o-8Wx.&7s-–[Efm*x @UKOD~WTOܱ1y{kƹ>vvƽ ޫ| `an@![.jwXd)eTb c{ౝ|9.|fQ EX'| ? /8j}"snApGq ώdN|}k>K'ϡVQ.O#l5s4tυ.H4,[muFz3񻃸ӅA⮊uXzWd>B?oM]:_.]-3师宱{{GFs"]+:1{Kaq~Wx]9J)Vt Wy*m^^֙z~˄ݮk=::b]]k ]L: |O%楀ys,t FxuE}`۬;}Ɍ]Е/Ib]%Ϯw Wy';t;#R$7fGGP@Gŏ{ ɣw7v.].rN9$:``uV~N%+Op+Ok Ař Rts$AfEĸX{ % J[{7Mqyy` G a˶GVx_i}$>BGVM+ly$9w筁>EƅvO8Nr1cH3v/|BwȒM//~DHt׊1`~0?zE ;S*tP.t3F0>u\uzc+wtX@Gb܄Ysd:]$ b|.*`/Vdelmhz;7"-oo/TpnQw+[NI>׷gɯmikС_ǛNWBY+<ڢb Fe[ѡ9z YRƐ`T4c zȤTPNc'|*t+/qI]}KutGё3o0WP0?t܋>yy&P:yM fY s:jW~Eɟ擜{O. !|ݩCKj~س|"<1 0`yO?E>䑃)r_;$˖zP}LD_׾̓os;Uزm*4c YZTśh +:K0I8yye:stNU;t!d7})(7sFFq|0P =ayYOks]5' {B5û]:GC cw??ţ.ֺFM))yv'N+꘽/yLs(Pw:]-6օ&G+ۨlw.].^l_c ]7ٱ0?`.] `o pcx>sj;>mcS{}|؝t|G#?w()I%* 000`~ tFN3qwH0>',WO* KDW'i`^~wJ"jG< 0ϤΦ;4}oCOLsJx>9ϫ=?z<=7ݒWstZĊvt)^>\^җK_R+@ݻ"< 7 N.oݿU$PHx;?\o= xNq;sfҊPn|ӗX]:.@ݻ"sUTfc8/0O#C0e2̙b`JŘK&NAc'+z$}/G/O뜑nxOaa~Cίs&#zK #&e `t(Uy%`^a`gy!`b g˭[ T~Ak(݅\:y3ih=h]h}h$χ)<0ڸƖLEE4s.D}\<Gh㻝|gH֭ț}i 30yy%o0|4m<ܯ ^@usaraw.ܭh(/ ܏O,7}>t o a_㹿.PP_mʉK\{*:t) w1OiGo:卞ݻz,ts6rqŵ/:Mfst{\.¯cFهWfY<Z/[>~Y e\p#n xouGp!U]Ԣhl#t_;l5.k^eG-96pڟ1p<sKKx`B`>22P֥ãG OlP|sfe'?M||/='M]o `_?O_\I}* ȼ_G M O=2.N'D``~0093}UG(_l`:v/s x<\=~s.yv>~`6Y*3VTASb &i ̿!_:z܏u^;Mܱݵ{v}u4ho}7!MVNnt>}^9w–[t[z|0g7#aAH]6`q  |C΂ |s8cT~3dda×1 glD}ѕp&iKEE/kŦOr')GTߡP w7Jsxp?M%]+B^vkz{}vg}qRd~se4- K;5:sE_I=ҘJP4Gb܇|ay"’Bx8X\4`GXP(!y0vAK;`k?txǰ^x=i]i}yK!dn{^ܲ•cmyL4qCsÝK:7Y@`؉Q{{Yo>}qu@}Ӿ=uQt@6$D+7(|\;X̱A۾ϝ{^e wtăvfCLE=~ݺ{F[H_[~;6N{v ZUr<ڜk[ )0L QM:y^@x:W|յ #a~^z ou~Э4mQrڻcJFRQ ̹D$ 2 (_r:OD %XAu@`wK~#1.״֭+t_ZцCxs +'jss1$$8:f0thڨ~W=^յW]{v;$ʺ1n/O9Q/$ G FH$tOhh!iQiqI0:+z$u?P/( ( ((X$ |4? 040C<0Jo3}7t.$X;޴\W65m +--=rؕV."蜦]5=#9EE7pSPcs@}+:ͧ-{sig:QE R F˼X+iWF;4کR^=: ~I\~`^kQH|<0̗g ɗ6M_t~^XobzѺ G"j]_8Ǿy+ot~_;M_b~Ėu<1|u't5ꇑ{Է#[r^M[T5Ӧ.},8SƢJ6I4^E`aNB4|z]>p /|cvSbrqutֳ5tvߦ\P; |P E_OpDfmI& &aOzM$zCz/w1;wmGw~o‡en}n]#śoSo 񈼦xWuF^ Fs >0031($KC5֭S(hquZШB\#x>Wn׀z: 6>H-*jU%gU vQidB CJ䅣VH0x%DEAюFBG"#p$p0`a~0`?w:7D B!GO ;}]n]\cġQ։=Z3yZP%)vn$OТÀԇG}~P_ᴏF(@Re;UtJ'D_Ls ?|#Un ~_l氭. 7zwh unju5kgsӗl7e} rǎ Syj" NhWwn>WH7,Fݻ5=2 Ǫ[:Yw%H"$*|< 0ܧwiǴEv:/D#/'m]e_ߪ[z$5)ö>k:]+bJژEQ[9G^FlE © qǎ ܦ#qw^b]L#V{iH8[osc'_n G{; 궚W~ etYE\,8هPN}v{NgꙀǝ# 9<5F1BQ#sxM!)C`>;i5uQ7.y!rNGo/oo?#w]P'$)Pö&>MS;9+:U,@=wECGأ2v~lWEuG,YQXnSrDqbpzG=dژS6=|@NxD{ttw~ݝstٺs$ 4:f9`ԅSD<\c)3nFt7i<_˜#*gҝK 6oO9|@},E#x̹z;uvpP_/7eAMՔiU-?,WHw.;O8# _vXu׹\?Pr;uPk[EH[H֡r?⧑gPۑU }p\k!a.=hqfͱ[쏸U y',>" OHcH*0̃2v#v O]Sˏ@ Yէgo;wʾ~y0c՛X.fSlE'6q[EgVM=ï9\>&gg ԥSS?4(}\uz7dzxVDt .!:U\>o0o7cq#ө]>PQoKr5#zDV+ &x e.N;bxդPGGQUwit}V &/@i ?/`WE握C\r5EĘ&qhy&71(K ay(' 3(yv[6o߰Nt׵޵^ߤnkg]ubnt̺.yե#*xl 5i;_CMꞼХpڿ$*謊,Lo^>o0đϙ.)e@}P9S3a|Pi5|IPͤձ^ = 7 Q ԋBtTXqԪ&ZdhP>y~>@=iZ!Y0{n FcGOL~]`>o0$9>} ~%Pk1Uv}Vj^QrQP2WyԤ5sy'QԮ"񆣰3zt ;UJb̅Y[˥uGR )bH(H_]0+ <0̧ m&/wN{]$O~|F%o9|{'/pG\*vԮv]y'Q#*(󀚖uk-5ٌ'ͯ].}t|o~رD`i6cq}`Nc?>i~GH~~8| QuL0:h>OK/jXXi@ nF:_ׯ%;P1'-.'N{wP m?,iM}e.0Wo̿E"o߆7uׄ{J)00]bmPAݪeUx9cM\w0@}3u魓^yw˛i즯`w(A/  0iD} |A> j;JtZ׳G_o 5n/_v"@ ?__P?^ &|@ݜy G.}AIko} fOs:N/ӇoQܫaCPc"zҺb}ic*yW'DE\fotD<(먚y;}y0_D٭?`O_|@pN˟1|NȒK]8/xl5۫.Jm qX;pM׷릾]REsXs\׶e:u۩_=>N֧BrND柆UDE(am^o:@17̯{}00߃,w'oڡ +'܍ ׹*r7-W}EYcҏ=\&MZEM_&\S_{)8oCrOқ7C*\QmZ*bر^&6uMk\0/ür" P o~.({ֺ6E nH QvKajڟO>Ҧ7HӨGL4n/jL+Ez0?n3~̷߁s"p ~P3@=Dk3z7ܥOCNɋIѥN|3z`| =no3W\z$V{JL}Yd19[17H4\B1&4HsHCcd:)2ݫ_t/轙k.}.K/=028~F7`wAgԡћ7kCa%ѣ S:Gaiߩ+ F/>DeSӗQLitxI3i939wЂFG0ԯ:IuyT+)VxM׿]ZxNaU 7mA]K߮&- ׫_UOTWY!yo:i b1 MYv E7Z_yHME _u"W  G"yJoK ̰74wu.]73KڇFFQudK3S9^t@= :WPĨXSZ[^c5ydvWyc?<yf5=yKEwhO'/lҤ[RH4 n?$Kg~VnjeʝhMUvQhjʻl5fK_ԻK_`j;ХqګO&Qt}l;̷yfwŗTX!v;=2e9}cF]N4nZ9FRPx6@K9 ƳRފijY;դK]K5?r̜A˛y%-hzlSɻ*1 mb n/_MmFvoB+9$lr7ȿ!Whwkf&Hf ].&ܥK/VG>O1iAŚvXyÜя:< fSP',9:b 8k X[8FO2E$!fiS/~8(B {?>>=y=NaSP.&k50K/҃]]Qm=s!ihICm qT=v|[S 92o]][yN:%15q4`Bo:.uWYKA(lI{].!iOxFg-\Vga9 u&3ǔnds4hp.$p*M,JԐ ! G4ygUX}㲊(T{i%Sc&̚bmi*y#2:{! xQCOtBr[oOrԵq#fALJY@g"]df 7] Cg=Wz=ϝ?^8Afou<ϝ9xe'o~uͼ?)BuSj .jbZb /Gy7ג%{PG,0cw]Chi`T>]5Fn0#aH~ccPc*zxmi#aH6-QNF]p@=N;S7aw`oc( :+NTe>VjξB3.5]SҟПIxIz}t*z ͓zh={ <>^'ϓ9 \+s]/fFpͼnJ]aThr°̑'T4TG&-ޥ&1Րk~ ~lDw0*nx%\Eua&0o H7:<!'"3K"wKo7KMS&1i"B8ݻ Hǵ6}M^w$M<`~ R7Goekt\ ?Audi 5?G_iL%@6/띩խ3د¦| x' Κdѥb}Lrü7CH:~y )'ztc үȿ<='#QC!#\o_@]L9ëKxua,XG|\`Ax9KwlcwDU@C: 3Iו6~RvU_8J0;aaXc^{x6jI SG.7i`>H]A# ްp`;9 Y'@_$b=CzW]) ^ qv]Gh~o}}k}Mk %eމx}MC /!;TPn(@k1Y;9+v!I}4_ep9giԔ 3v_[M\`/գ|&!T nbEѣ G"aHP$btg>%YM'?YПIxI 9+"E"ЃH|{G|B `O]ޢ{ woC-栖j~M> D'ѡ ЇKx::n0+Πvjz?jɋt K﬚݊ZrLM8&-ݏZ[=`;z=^+@;! Zk~6v?b;%zz)}֎ I %!1-GguX ^yzL@_/-w}>nDϕ/=gz]ujJ.{DBb0a%sM'qx:m 5% ד=ɇ'ަ~I> K0~-7}֓zG$0>ԓS\O꩞'W|t=@CuBN( y P$`0 LЧ 'vO Ї$<$z|9+\{䖮'Ũ'UPy5=ժ9z5q~FԂɗkt %fI7:t ~@i٧jrQԔ}'M=f݅ %fxԔ9::u 5堩)gj]Bbh!@wBtNNXyBc7)9zY55j15)djnԔTfOm@I/ &f$$FWPPxLA#Tg >@? ?? ?{Dϕ/=gz]u;%uEb􅽦t8)T>@{NMjU35П^5e7jv[QS7|G)vԑ8ql;v}%j8`xY(K5wJTü?cfY* x `{@?=x wr` M7Ň|~I,=c.}}@_? $Ѓ:UWY[}HMK0bi澝ilFW+ th= tz?U s%C_@`&ГN7guM} MN@OB:ܾrq\q9: #I$ts@OB:Ù@%@?3'{vm,@`ʯȝ,on0 oa=<<g@_bzMR t_gow1mSuE~T@Yлi_6?>tÚ@OB:ܮ)2Я:ݐ&ГN7 mX|c )y/˳0_n vsWr{@}'@ Mz}-}%Dχe\e|]ceeeXS膶n}[n}' @o7Z@4Jo=e~ )?IӍn= tS;@_ 8g>~y]|7̓[ M7o+9,} ؝@z"v~mz4 ݐ&ГN7 ctqᴳ2sk)nTI@ign=MxN7 $DM4pH#_瑯H@%`{Gt÷ݜ}+y,,y--y.~  @$ \KXO$uUC}@^@л:ЎU]UmqqAK˹m;ch|Y{ o}@ o@_WA|X:Y[]_恵nlK]d]rS)]@@@_[Op F;nПП$F4t@r @?Ex =nhI@ t,pwc=} or!ҍ`= ts>r|80}.? q}7 @oG,7|@_i@"2222222!tc[lnMՕǮ_ oM7#8yS__3Kw\b|ϻobo= tsz4E#'bX;e1cwй 0t])iߘ0r'Kw<%xt>DM MԷ<|Xg^@лN@,2222ގ;t_SnLttaЮ+.3]0z?޷0лVd:-;b OǂzlႤm= tzc;Ty=@pL<@Ga '܎SU}w"]:&ГN7+kMM S6j+#_9?v*;(&ГN7)jʶsnMwk mM 蓋r˩msai4tyR#\wO4[NrmۼcTq~t@OB:ݜ노2K./GewPSwWݚ/6ӏnpyɎ :: 21t|OSg{,`nzw[bt{ֿe׾nVl:9`ik' ;tC_Onv+F897A8fz7}!%EԔ'L{g]#zKs,LKj= ts˻{F89<~ d>?ӹV<6&0O܋yjpE}prw#ݻwwtM'!n>jpy<֓.@=6[=y֓n=X[}?r :c)B8oG^@T@OB:|zq{ӷwܾ^Z5n~' }r];vi= t󹲖ܜvܾ.7?<9/wqqcw.P:&ГN7m8nG-%&kYȩ߿]Ƞ܍~S9Kwx>)64vsr9d a?ӇE؋E wznFaW_n${uD'Fv)6Kw_7KQ2.A-9az6Zrȫ%_|&z %xtF:%sN $zAM:r֑kq{iӻpt;hּ %QGyksR/$A1f}N7mN}udl=gw~u;no_\ʀ?"ܥ_EKl= txǮ :n?g֑wϟ6[/G Kv-XB#/ trO{g@&@ǝf}Qӳ@_#ϽWb!B\#Ѓo3>r_%;+k(!rܾ}Ԑk`{~{7qկG_2::\[g)Ɖo=8.Pz{*+,+~s~}+C˟#:otثIuOOLT{~-o4a9܉<]:a1^s1vL-FyEnt \ ufvp{ Fr7w;{S`Spgrz6N|wgv3w :D:(cyvY|C䣛ep׵Im9ozM{aӵMӹU%ȋm,@OIL]}n{<:֏gljQ?Q? GP?~1nWǾds?KإW?v!9N@\k r |TKǢ$7_kZjǞ[ Xkoslv!x@zݫϥeO5[/]%]vNIrQts:r:nLF`Q0=GfE{zO=,'r20 ?fw|QՎnZwױxu;no[3I9]jNawu3v\tܞlwQ^4rs:u};6sw/[@ Ԇ?Am@m1 Nso<0Mqݱ1qݾmIǪx.7'u᳨ ف>UwAt=;;m# ?ztd~C=nw!@䋸;YշS/\>2ܥb;]}.PCWƎ~iOc<]kxk׿tw ӽOm94j¿F;=Kɧw烾\xKm˟W3I:FM丈8=׳ݶo^~|;K.`w_v~}uAYlAG&Ǐ޻5zPQ\Aeϳ;?&xpږ7i>VSw@GKة_EǯAyx Ƈf;02@RXņcm/n>77̀o[߾y&vp*w޲X0,VvL.K=@޽7=pApNp\j鶦V\6}[. OyկG_?ogOawހ@x|Q,N%w4"W.b.d:.϶Ho7;7  orww^fowc7?Xa.u`go#Nm>kcܜu.Nool`QN7rs۹MӱUӾ%&/-;]vna;w\t./ԏ\,y[;oA΀܁c VOzw}|_eG> sMy5nٝq׃'v؝ww˟{ '/o%Խ??"x]Ըt9]q]^wߕP/~u ]>@m/w}#䗽u_S::Kߝ~LKܱd_;ɣ3yF2Ы@*r\One:ȯxk^n]w3ϵOϿ;_'i&-ܱ|g锸r>Ƨ~uw!_ZضPwd\_PƲbg>FN+n;6fuΟYz7_iGE>[\tV@}7|p/ y ;aNs<ܝ ,yezㅞ;z),Bz6hynlCYY,y8-/';.%W]žuڵLqrod[Tvo'4-^a7qڿwEӓ?k74]N;;oγ8˞n U]&2 ^y}Ɔ!=+n ԕ0qyr]7MuwwLv_swކy۝8ۋ I38tͧ9wHn.>OwE ~ǂigcV rœNO?4;ldSړ? ; 7;ݝgW Wawz69 /p#/ 4LR}`w?22_d MGh_=G<8[4[~qg:Zn.Oۯn z629v+M Η?o}"iƢ %t8Y u:~Ws~zm,_퍺l(]&Q/M\^\GMwLZWcw(vveлx?l>LO)\`Nziz6o}s7;OG7syNv"W;rg%Եa> (]QX*dԓNi=o }T_h>@4Mӳ;W3`Uko>74sW_Qx ΤY\w]NA|[N0)ڣ?yi^,.Œ7-̯m;w0? ̏bg6v#y={4+BkuGWvn8lm޳2v^@O&bȀ,LP[߂}7Xنi^p  7M`. S-݀yצsC懱3?k&fem̿4'N&@2ԻtP*7,oz:N49̏MV^`fknSIsaվ1 o6Թ[i^8+ۮ%%w(l57s拗h5˞x\lh_Xw`ӡP'iu0\z0?mdo0o[*`>MV%ækkG}ZW&.ֿs4MM>}gG̏ooo0^U $]O>۵{.Dj1I/|G4MM﹞a'&a>Kpֺ/%;T#3tt7Kok vG.v pYW 00=ßvR'dGOZ#i_mni۳a&f/7 寧h6`Cq 09g,w}aliq=b]W/w{oph?ߚ{.{-i-8;zwdž?x.%-G+rW&G-2^hyA߇`|?淡iSa_mZmpˋoο{G=0/L{8< /?i]-Ik V;׽ٱ_wnǮOzr-蛀k۫ZO$(Fw11vniQu |7&c?K޳9᫁ݾ<];MӴ5ݕr]ߎxry}}\5Bo'#*$9?=}Ks}[ w\ ?'qHLYyhoCvW>[s7iPm͑/;cɷ:$9$IOn9ػ%$E $ZkDǛַL.qL)﮴inBWԺ:^׷Λ^=E]u5u=^|pʴcWގ]yݕgW4W߰+o]1~u^|aѲJ7Tʮ:5oݶ{]^*{쿜 J wFn6A{T \ױ+o_w̠vroW[EYl=~O&*F!)Wo^я۬7;wintpg[]ˮ<װ+EIsJPH;_``*=( {(gߍO\6Jsr9^n]{_-aWؕʿ4O `~ tQ3:*4MGjVUԳv xkؕǮ{;i$݂]~Vp]4ܹx=Z_3ݳƽֽa]}wi]E۱+oǮIsj!;mkn_{Ӟ{ݿ{<4M >[/y5rr7~~n3@{N>u!@j[$)H"i+~wy'ߔc>𕐟 4MQM֧?ޑlq NQw+_?.ʽ.Z|[<\ٕo|"޺mỰkϹ?hV8S.wg챼$_z,,I-? ?[(.ϐ$Wu?I@43Yuufm-_9ICskFlx6jntk|ue3rh} 9Z܁oww->4&+m.ݶ5 ?& wII>;U x 'aO4uhARpC}@\~-@\57*AZ셻V#$菐?]?${w߽_!U\Q\x[]xjA|j7׮.~u?Ԛ{*]}xvHmk'η=vM$ɱ{oBt[8X(X0]iQݚ֮gZ&5B57⨅؍/[o} Eǟ]ד5-}[HoB2/GRgFr*w3vLw7L4g9Hj4rklVi M e͢/m\=^TuTB2߼C#>2,|i:Fꏅ.WPòe/# YȢe/rWN5:11ŏ}X 7r̳=Mtg=Bw˻#o!~1 c@|{ҵ"Ww |,EI=*Ȕ4MGhAo.5q;;q;pw{/INW4M )Ov (EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE5daIIQ aS E\$'0EThqRTB9>t.v@gVMT [P8N5.v@:"Щ$}OObjX! 'Щ"NqR@]:D/`ɟI6ULS +oA:RI:N*T@oET1N5ETSHb'8DS.ZH"*&Щqj )r]$'tES IC=TMT [P8N5.v@:"Щ$},¦ ta-(r@B\;IIEx*&Щqj )r]$'tES I_MT [P8N5.vBw_6ULS +oA:RI:N*T@,qbjX! 'Щ"NqRhhh* t|pkEѯ83Xr_&sTͷ3˘coL21VP8N͐̿SSn-h tʤ*{~.qwk],+ce"P|c2Vx#8S21v/l^KfOr]+tj&Ot& y7c sZti6U<'Zax.)Ucr8`~Я$gxLF|1mte_C[ƢRDweLFvl&0.f]E/(ډv 5l7ktemmu[S{cQpX}T&&:kv3^'k,tipp*to\R|s1C: BXZ X}((ڈvGyBrRym;ɓZړC~k3qSS,6/]rj3z[Y F+>w01! 1d 8j74=Lk0^j &k*qjּ8ǻj``*}^Q88 'ԯ<'r uR @\fDhsTWh@cnO sCd1Hn*؆ƨYs͖"lxspuƱ'<ϡ>_Pap@G>ޘ=IVgHW8^ŧ$T}}}ET(K-ka= mHHll^ϼ|徖}}홺Kq'*ƥrLrS^.EQ^ʮl;YgiOO{m>jkY9e>HހWqDRΉm&"Uooo6U<*fV0yxBѾߥ meJҿ,/hñ} 8\5F;yƥ.kWC=mT0wu>芚! vVjYup˞hvlCSb9mfOHSM.yf{dók@<dÎ"X~8d\ cSn1 2~u8)p@Z\]$ZVG-ʹV}qhul#l'NqizOjHα+AvhO bhQi81n&z5a(0mk0^eLHqhaWvoqOvb ۘ&^Vwww65;!i?poH^u EWyKoӧ5;Еce}X&œK>H>ܶM'vu+ST ~Ū"lGrG.PyȄnO혧GoBEj*\0~2!O;m* X,S\jsBh'̷R54U,& 4#J"Q9,s`c90c`3Y`d#f t:-eFJGhl -M"9mdnΗ(&'HqLx2QЗ-G;qzޣ.h2toT\ñmj%ֲjw-SԖfsa7O5ZS"l8b/TQ ))JTfBE@Tv!iJm* XZv4  Z"K=Tм/ÕVұ jQƔ?ָr,_+e,{ڜhs4µX4m9aeӮLXmV?lER6-FBJcs+J/9E뾓z{sUqpXe\m* Xkل2LmZ3д <+1ŋQډ92BqE)eXߺmÿD"@kqN&\+5;Еcqs ]elye\uC/ZOSeJSc6[5d/E~?7?/>jo;ʱr#.qt\k,"@-{8!t氲\jvMD*iKq2vZ2gb:Ռ[)*S'xzHنRjYh[M{0k{E("vbǁ^x.) uA;M@5o(ps6G;\kL9_ &ND(8e~:nϨIGT™*l x\K9Iٜ\ØUx-ef t9+4Zc9 VU]'eNHݪl[uQiޯR~bm("hہǕ횿Jm8!gbr-]S 2؁6Gqt\kL^bd*s$v>lGo}*N7C+ V\OnRl!6Ҥպ"hs4u;h/P_F_u]نRfkB~;oyYFSHPM':0h1%asq&1MZG!qO۸(3vQE\Sv2 ui WsqcCf6U"11)Jkx(8W؁̳Ѐ.u*P&eNH]e 5D(,+oaSʼnG#>Kv4LظGִQ3m,Uۨg<$Ҽ-b\ñLˡ]׉]Ycڜ(s KsŠ/3e:ETqo E5%Eόر5{횁ncFOT&gÔr ^n( ʼN+sm8!ui28rYˑ/׿^M'CGk)_v}5q t圛(ЏvNyPLcA&kRWh@WD7'ʼkLm8!ui8B۶6jbk_Z6U)Tacpo~e,5ǁU k (hs\.ȉ 8Q+p@GB]K v]cl [ő Յh{b^6Uo+״ 7*/&E_}/sPRX)p@GQ9Si#(󩮯)pռq(뫪|*QS-¦:(jvVy18$A;2"cl kIHޕd&uvT+4+K1@Njֶ̯f(|L&S‰]G_k-j釲54 )yFxPUSc?j1Uq1^ i]]?Zl [biQƓr_aP|ye|>3n0@kd;7-ߔ%.375oomN( 迣[LXmOQ-MiֶY]1pB&zjxSj|٭I<>Q;AkBW]Ǖ4 &CAyeYN/1 ֘BXmO랧4/&MgfUu_r6 Ǿ[C-Ү"7LV(UoLu߫l'RPPj!8`N ˥mr|B:5l3kHK^VĤY3r[Fٟ2\Y_]96mQ(paucWI5} r\E>8b)2 UƔГeڵ]#4,1(U|N;n'"l.) ⺱jV <`.mOV&n&"_P1Ю-@n{˰2'**)c $v&R qiXSf2\Y_җQ:66&8NY5]KWƨ>BE|f,"Ѣl?)ǻЀp>\ێ2Pl qeekkb.V[UMbk2Bwτ۳YciC@0b'&"Ej*iY?\Rvh@a qBj/Yy-I%їT77 8&'@<5łe׏|W6 @עp ^RoͻP9 LmWđ8 Zj{Pl b)"w4/aБc8j*>ۢl#\aB: fC .(Vqg (T+ڭ1C+߅ڟ%1qϋ='ƦqKooaSsSC]GfH~$!Pݸ̩_{a!$;[d kЀkոb'Hm -$/ȸDfN^MvB%1#*|~П=YncZQy vl/uݸEB)rBڶj!3C P s=XA'_60 _w.>V_áXᒲM;'63wsC-@5 3x2^;u=>_P'4);:vFwXqXSF fB?MNmcOƍ/U B^mּ( 8$\}-Cj64~l cs?F mhl xy$R;Jv)ڋekXC l ݀14oJ狰Q]}IѾphwfp~k&Ё.Z|-yͬ]Wzژ|1vk'm6?W{!0iSݸ_Ug욝/#9ѴDv%&jTğZs=ET(+V<sשt v"㙰cS}ul_5u؎o#yQ̗9HIOuh#ljϺ|뫼v7j kmqVmϮ1'ZִDv8֚IǞZگZ6U_Ps\I8Fyluڊrjbpj?@ZW\ұV---ETKn$ЯeZl-Dmx<1.E~a{6QgBYN`e8(cJ9gǗw]MV|&LX.D#د-ZXJ7|h#ﴢ '`IȟǶQM7ފĹKwP˔8QǡY—5~. OP$"WEP)/| s=o*nc +@^1#ƻsYۏHs΄*㠉lj#V[M~W*)>%8'r"183Oۚkzcc۹j\f/(ڊWdx=c`ۛhιU]m 6_jjϮ;kXVe\N1'm=H:s\6UO0Gc6GZ]pk^8VR\y9t Ngsl.ٿS…*m~>}WvǦ<>pMSQd,H>gRq=Q@o5Բb3(4$dlق )'}#N}ׯ|E2د0d2y}AOg.7Z. Ye&kY}Y(cYpEQ E+2*&R$GA1gԞ QEQ!뮻M)P_(ZH"Ka|sIJQEŨ|3cb=%RW EQ I`Sz yps%.$+EQaT LұREQ1Sl@OXbEQդ'?9*&rNұREQ @O0y%U5O|cb=!apI1Gb'x)DS0s;s 6UL(X9))o'~QEQ @_5CQEͯcb#Da this.options.limit) { this._createNavigation(); this._setPage(); } if(this.options.onCreate) this.options.onCreate(obj); return this.obj; }, _createNavigation: function() { this._createNavigationWrapper(); this._createNavigationButtons(); this._appendNavigation(); this._addNavigationCallbacks(); }, _createNavigationWrapper: function() { this.nav = $('
', { class: this.options.navigationClass }); }, _createNavigationButtons: function() { // Add 'first' button if(this.options.first) { this._createNavigationButton(this.options.firstText, { 'data-first': true }); } // Add 'previous' button if(this.options.previous) { this._createNavigationButton(this.options.previousText, { 'data-direction': -1, 'data-previous': true }); } // Add page buttons for(var i = 0; i < this._totalPages(); ++i) { this._createNavigationButton(this.options.pageToText(i), { 'data-page': i }); } // Add 'next' button if(this.options.next) { this._createNavigationButton(this.options.nextText, { 'data-direction': 1, 'data-next': true }); } // Add 'last' button if(this.options.last) { this._createNavigationButton(this.options.lastText, { 'data-last': true }); } }, _createNavigationButton: function(text, options) { this.nav.append($('', $.extend(options, { href: '#', text: text }))); }, _appendNavigation: function() { // Add the content to the navigation block if(this.options.navigationWrapper) this.options.navigationWrapper.append(this.nav); // Add it after the table else this.obj.after(this.nav); }, _addNavigationCallbacks: function() { var paginator = this; paginator.nav.find('a').click(function(e) { var direction = $(this).data('direction') * 1; // 'First' button if($(this).data('first') !== undefined) { paginator._setPage(0); } // Page button else if ($(this).data('page') !== undefined) { paginator._setPage($(this).data('page') * 1); } // 'Previous' or 'Next' button else if ($(this).data('previous') !== undefined || $(this).data('next') !== undefined) { var page = paginator._currentPage() + direction; if(page >= 0 && page <= paginator._totalPages() - 1) { paginator._setPage(page); } } // 'Last' button else if ($(this).data('last') !== undefined) { paginator._setPage(paginator._totalPages() - 1); } // Handle callback if(paginator.options.onSelect) paginator.options.onSelect(paginator.obj, paginator._currentPage()); e.preventDefault(); return false; }); }, _setPage: function(index) { if(index == undefined) index = this.options.initialPage; // Hide all elements, and then show the current page. this._rows().hide().slice(index * this.options.limit, (index + 1) * this.options.limit).show(); // Set the current button as active this.nav.find('a').removeAttr('data-selected').siblings('a[data-page=' + index + ']') .attr('data-selected', true); }, _currentPage: function() { return this.nav.find('a[data-selected=true]').data('page'); }, _totalRows: function() { // Count the total rows of the selector return this._rows().length; }, _rows: function() { return this.obj.find(this.options.childrenSelector); }, _totalPages: function() { return Math.ceil(this._totalRows() / this.options.limit); } }; }; $.fn.paginate = function(options) { switch(options) { // Example of custom actions: // case 'destroy': return pagination.destroy(this); default: return Paginator().build(this, $.extend( {}, $.fn.paginate.defaults, options)); } }; $.fn.paginate.defaults = { limit: 20, initialPage: 0, previous: true, previousText: '<', next: true, nextText: '>', first: true, firstText: '<<', last: true, lastText: '>>', optional: true, onCreate: null, onSelect: null, childrenSelector: 'tbody > tr', navigationWrapper: null, navigationClass: 'page-navigation', pageToText: function(i) { return (i + 1).toString(); } } }(jQuery)); assets/js/social-admin.js000064400000001615147577535410011405 0ustar00 jQuery(document).ready(function() { jQuery(".loginizer-social-wrapper").sortable({ handle : '.loginizer-social-grip', placeholder : 'loginizer-sortable-placeholder', stop : loginizer_sort_social }); }); // Used to sort and save the order of Social Login Apps. function loginizer_sort_social(event, ui) { var target= jQuery(event.target), sortables = target.find('.loginizer-social-provider'), sorted_arr = sortables.map(function() { return jQuery(this).data('provider'); }).get(); // To show the saving order text. let saving_order = jQuery(ui.item).find('.loginizer-social-saving-order'); saving_order.show(); jQuery.ajax({ method : 'POST', url : loginizer_social.ajax_url, data : { security: loginizer_social.nonce, order: sorted_arr, action: 'loginizer_social_order' }, success: function(res){ saving_order.hide(); }} ); }assets/js/chart.js000064400000615310147577535410010151 0ustar00/** * Skipped minification because the original files appears to be already minified. * Original file: /npm/chart.js@4.2.1/dist/chart.umd.js * * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files */ /*! * Chart.js v4.2.1 * https://www.chartjs.org * (c) 2023 Chart.js Contributors * Released under the MIT License */ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";var t=Object.freeze({__proto__:null,get Colors(){return jo},get Decimation(){return Uo},get Filler(){return ha},get Legend(){return fa},get SubTitle(){return ba},get Title(){return pa},get Tooltip(){return La}});function e(){}const i=(()=>{let t=0;return()=>t++})();function s(t){return null==t}function n(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.slice(0,7)&&"Array]"===e.slice(-6)}function o(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}function a(t){return("number"==typeof t||t instanceof Number)&&isFinite(+t)}function r(t,e){return a(t)?t:e}function l(t,e){return void 0===t?e:t}const h=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:+t/e,c=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function d(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function u(t,e,i,s){let a,r,l;if(n(t))if(r=t.length,s)for(a=r-1;a>=0;a--)e.call(i,t[a],a);else for(a=0;at,x:t=>t.x,y:t=>t.y};function v(t){const e=t.split("."),i=[];let s="";for(const t of e)s+=t,s.endsWith("\\")?s=s.slice(0,-1)+".":(i.push(s),s="");return i}function M(t,e){const i=y[e]||(y[e]=function(t){const e=v(t);return t=>{for(const i of e){if(""===i)break;t=t&&t[i]}return t}}(e));return i(t)}function w(t){return t.charAt(0).toUpperCase()+t.slice(1)}const k=t=>void 0!==t,S=t=>"function"==typeof t,P=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0};function D(t){return"mouseup"===t.type||"click"===t.type||"contextmenu"===t.type}const C=Math.PI,O=2*C,A=O+C,T=Number.POSITIVE_INFINITY,L=C/180,E=C/2,R=C/4,I=2*C/3,z=Math.log10,F=Math.sign;function V(t,e,i){return Math.abs(t-e)t-e)).pop(),e}function W(t){return!isNaN(parseFloat(t))&&isFinite(t)}function H(t,e){const i=Math.round(t);return i-e<=t&&i+e>=t}function j(t,e,i){let s,n,o;for(s=0,n=t.length;sl&&h=Math.min(e,i)-s&&t<=Math.max(e,i)+s}function et(t,e,i){i=i||(i=>t[i]1;)s=o+n>>1,i(s)?o=s:n=s;return{lo:o,hi:n}}const it=(t,e,i,s)=>et(t,i,s?s=>{const n=t[s][e];return nt[s][e]et(t,i,(s=>t[s][e]>=i));function nt(t,e,i){let s=0,n=t.length;for(;ss&&t[n-1]>i;)n--;return s>0||n{const i="_onData"+w(e),s=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...e){const n=s.apply(this,e);return t._chartjs.listeners.forEach((t=>{"function"==typeof t[i]&&t[i](...e)})),n}})})))}function rt(t,e){const i=t._chartjs;if(!i)return;const s=i.listeners,n=s.indexOf(e);-1!==n&&s.splice(n,1),s.length>0||(ot.forEach((e=>{delete t[e]})),delete t._chartjs)}function lt(t){const e=new Set;let i,s;for(i=0,s=t.length;i{s=!1,t.apply(e,i)})))}}function dt(t,e){let i;return function(...s){return e?(clearTimeout(i),i=setTimeout(t,e,s)):t.apply(this,s),e}}const ut=t=>"start"===t?"left":"end"===t?"right":"center",ft=(t,e,i)=>"start"===t?e:"end"===t?i:(e+i)/2,gt=(t,e,i,s)=>t===(s?"left":"right")?i:"center"===t?(e+i)/2:e;function pt(t,e,i){const s=e.length;let n=0,o=s;if(t._sorted){const{iScale:a,_parsed:r}=t,l=a.axis,{min:h,max:c,minDefined:d,maxDefined:u}=a.getUserBounds();d&&(n=J(Math.min(it(r,a.axis,h).lo,i?s:it(e,l,a.getPixelForValue(h)).lo),0,s-1)),o=u?J(Math.max(it(r,a.axis,c,!0).hi+1,i?0:it(e,l,a.getPixelForValue(c),!0).hi+1),n,s)-n:s-n}return{start:n,count:o}}function mt(t){const{xScale:e,yScale:i,_scaleRanges:s}=t,n={xmin:e.min,xmax:e.max,ymin:i.min,ymax:i.max};if(!s)return t._scaleRanges=n,!0;const o=s.xmin!==e.min||s.xmax!==e.max||s.ymin!==i.min||s.ymax!==i.max;return Object.assign(s,n),o}class bt{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(t,e,i,s){const n=e.listeners[s],o=e.duration;n.forEach((s=>s({chart:t,initial:e.initial,numSteps:o,currentStep:Math.min(i-e.start,o)})))}_refresh(){this._request||(this._running=!0,this._request=ht.call(window,(()=>{this._update(),this._request=null,this._running&&this._refresh()})))}_update(t=Date.now()){let e=0;this._charts.forEach(((i,s)=>{if(!i.running||!i.items.length)return;const n=i.items;let o,a=n.length-1,r=!1;for(;a>=0;--a)o=n[a],o._active?(o._total>i.duration&&(i.duration=o._total),o.tick(t),r=!0):(n[a]=n[n.length-1],n.pop());r&&(s.draw(),this._notify(s,i,t,"progress")),n.length||(i.running=!1,this._notify(s,i,t,"complete"),i.initial=!1),e+=n.length})),this._lastDate=t,0===e&&(this._running=!1)}_getAnims(t){const e=this._charts;let i=e.get(t);return i||(i={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},e.set(t,i)),i}listen(t,e,i){this._getAnims(t).listeners[e].push(i)}add(t,e){e&&e.length&&this._getAnims(t).items.push(...e)}has(t){return this._getAnims(t).items.length>0}start(t){const e=this._charts.get(t);e&&(e.running=!0,e.start=Date.now(),e.duration=e.items.reduce(((t,e)=>Math.max(t,e._duration)),0),this._refresh())}running(t){if(!this._running)return!1;const e=this._charts.get(t);return!!(e&&e.running&&e.items.length)}stop(t){const e=this._charts.get(t);if(!e||!e.items.length)return;const i=e.items;let s=i.length-1;for(;s>=0;--s)i[s].cancel();e.items=[],this._notify(t,e,Date.now(),"complete")}remove(t){return this._charts.delete(t)}}var xt=new bt; /*! * @kurkle/color v0.3.0 * https://github.com/kurkle/color#readme * (c) 2022 Jukka Kurkela * Released under the MIT License */function _t(t){return t+.5|0}const yt=(t,e,i)=>Math.max(Math.min(t,i),e);function vt(t){return yt(_t(2.55*t),0,255)}function Mt(t){return yt(_t(255*t),0,255)}function wt(t){return yt(_t(t/2.55)/100,0,1)}function kt(t){return yt(_t(100*t),0,100)}const St={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Pt=[..."0123456789ABCDEF"],Dt=t=>Pt[15&t],Ct=t=>Pt[(240&t)>>4]+Pt[15&t],Ot=t=>(240&t)>>4==(15&t);function At(t){var e=(t=>Ot(t.r)&&Ot(t.g)&&Ot(t.b)&&Ot(t.a))(t)?Dt:Ct;return t?"#"+e(t.r)+e(t.g)+e(t.b)+((t,e)=>t<255?e(t):"")(t.a,e):void 0}const Tt=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Lt(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return[n(0),n(8),n(4)]}function Et(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4-n,1),0);return[s(5),s(3),s(1)]}function Rt(t,e,i){const s=Lt(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i*=n),n=0;n<3;n++)s[n]*=1-e-i,s[n]+=e;return s}function It(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=Math.min(e,i,s),a=(n+o)/2;let r,l,h;return n!==o&&(h=n-o,l=a>.5?h/(2-n-o):h/(n+o),r=function(t,e,i,s,n){return t===n?(e-i)/s+(e>16&255,o>>8&255,255&o]}return t}(),Ht.transparent=[0,0,0,0]);const e=Ht[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}const $t=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const Yt=t=>t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055,Ut=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4);function Xt(t,e,i){if(t){let s=It(t);s[e]=Math.max(0,Math.min(s[e]+s[e]*i,0===e?360:1)),s=Ft(s),t.r=s[0],t.g=s[1],t.b=s[2]}}function qt(t,e){return t?Object.assign(e||{},t):t}function Kt(t){var e={r:0,g:0,b:0,a:255};return Array.isArray(t)?t.length>=3&&(e={r:t[0],g:t[1],b:t[2],a:255},t.length>3&&(e.a=Mt(t[3]))):(e=qt(t,{r:0,g:0,b:0,a:1})).a=Mt(e.a),e}function Gt(t){return"r"===t.charAt(0)?function(t){const e=$t.exec(t);let i,s,n,o=255;if(e){if(e[7]!==i){const t=+e[7];o=e[8]?vt(t):yt(255*t,0,255)}return i=+e[1],s=+e[3],n=+e[5],i=255&(e[2]?vt(i):yt(i,0,255)),s=255&(e[4]?vt(s):yt(s,0,255)),n=255&(e[6]?vt(n):yt(n,0,255)),{r:i,g:s,b:n,a:o}}}(t):Bt(t)}class Zt{constructor(t){if(t instanceof Zt)return t;const e=typeof t;let i;var s,n,o;"object"===e?i=Kt(t):"string"===e&&(o=(s=t).length,"#"===s[0]&&(4===o||5===o?n={r:255&17*St[s[1]],g:255&17*St[s[2]],b:255&17*St[s[3]],a:5===o?17*St[s[4]]:255}:7!==o&&9!==o||(n={r:St[s[1]]<<4|St[s[2]],g:St[s[3]]<<4|St[s[4]],b:St[s[5]]<<4|St[s[6]],a:9===o?St[s[7]]<<4|St[s[8]]:255})),i=n||jt(t)||Gt(t)),this._rgb=i,this._valid=!!i}get valid(){return this._valid}get rgb(){var t=qt(this._rgb);return t&&(t.a=wt(t.a)),t}set rgb(t){this._rgb=Kt(t)}rgbString(){return this._valid?(t=this._rgb)&&(t.a<255?`rgba(${t.r}, ${t.g}, ${t.b}, ${wt(t.a)})`:`rgb(${t.r}, ${t.g}, ${t.b})`):void 0;var t}hexString(){return this._valid?At(this._rgb):void 0}hslString(){return this._valid?function(t){if(!t)return;const e=It(t),i=e[0],s=kt(e[1]),n=kt(e[2]);return t.a<255?`hsla(${i}, ${s}%, ${n}%, ${wt(t.a)})`:`hsl(${i}, ${s}%, ${n}%)`}(this._rgb):void 0}mix(t,e){if(t){const i=this.rgb,s=t.rgb;let n;const o=e===n?.5:e,a=2*o-1,r=i.a-s.a,l=((a*r==-1?a:(a+r)/(1+a*r))+1)/2;n=1-l,i.r=255&l*i.r+n*s.r+.5,i.g=255&l*i.g+n*s.g+.5,i.b=255&l*i.b+n*s.b+.5,i.a=o*i.a+(1-o)*s.a,this.rgb=i}return this}interpolate(t,e){return t&&(this._rgb=function(t,e,i){const s=Ut(wt(t.r)),n=Ut(wt(t.g)),o=Ut(wt(t.b));return{r:Mt(Yt(s+i*(Ut(wt(e.r))-s))),g:Mt(Yt(n+i*(Ut(wt(e.g))-n))),b:Mt(Yt(o+i*(Ut(wt(e.b))-o))),a:t.a+i*(e.a-t.a)}}(this._rgb,t._rgb,e)),this}clone(){return new Zt(this.rgb)}alpha(t){return this._rgb.a=Mt(t),this}clearer(t){return this._rgb.a*=1-t,this}greyscale(){const t=this._rgb,e=_t(.3*t.r+.59*t.g+.11*t.b);return t.r=t.g=t.b=e,this}opaquer(t){return this._rgb.a*=1+t,this}negate(){const t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return Xt(this._rgb,2,t),this}darken(t){return Xt(this._rgb,2,-t),this}saturate(t){return Xt(this._rgb,1,t),this}desaturate(t){return Xt(this._rgb,1,-t),this}rotate(t){return function(t,e){var i=It(t);i[0]=Vt(i[0]+e),i=Ft(i),t.r=i[0],t.g=i[1],t.b=i[2]}(this._rgb,t),this}}function Jt(t){if(t&&"object"==typeof t){const e=t.toString();return"[object CanvasPattern]"===e||"[object CanvasGradient]"===e}return!1}function Qt(t){return Jt(t)?t:new Zt(t)}function te(t){return Jt(t)?t:new Zt(t).saturate(.5).darken(.1).hexString()}const ee=["x","y","borderWidth","radius","tension"],ie=["color","borderColor","backgroundColor"];const se=new Map;function ne(t,e,i){return function(t,e){e=e||{};const i=t+JSON.stringify(e);let s=se.get(i);return s||(s=new Intl.NumberFormat(t,e),se.set(i,s)),s}(e,i).format(t)}const oe={values:t=>n(t)?t:""+t,numeric(t,e,i){if(0===t)return"0";const s=this.chart.options.locale;let n,o=t;if(i.length>1){const e=Math.max(Math.abs(i[0].value),Math.abs(i[i.length-1].value));(e<1e-4||e>1e15)&&(n="scientific"),o=function(t,e){let i=e.length>3?e[2].value-e[1].value:e[1].value-e[0].value;Math.abs(i)>=1&&t!==Math.floor(t)&&(i=t-Math.floor(t));return i}(t,i)}const a=z(Math.abs(o)),r=Math.max(Math.min(-1*Math.floor(a),20),0),l={notation:n,minimumFractionDigits:r,maximumFractionDigits:r};return Object.assign(l,this.options.ticks.format),ne(t,s,l)},logarithmic(t,e,i){if(0===t)return"0";const s=i[e].significand||t/Math.pow(10,Math.floor(z(t)));return[1,2,3,5,10,15].includes(s)||e>.8*i.length?oe.numeric.call(this,t,e,i):""}};var ae={formatters:oe};const re=Object.create(null),le=Object.create(null);function he(t,e){if(!e)return t;const i=e.split(".");for(let e=0,s=i.length;et.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(t,e)=>te(e.backgroundColor),this.hoverBorderColor=(t,e)=>te(e.borderColor),this.hoverColor=(t,e)=>te(e.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(t),this.apply(e)}set(t,e){return ce(this,t,e)}get(t){return he(this,t)}describe(t,e){return ce(le,t,e)}override(t,e){return ce(re,t,e)}route(t,e,i,s){const n=he(this,t),a=he(this,i),r="_"+e;Object.defineProperties(n,{[r]:{value:n[e],writable:!0},[e]:{enumerable:!0,get(){const t=this[r],e=a[s];return o(t)?Object.assign({},e,t):l(t,e)},set(t){this[r]=t}}})}apply(t){t.forEach((t=>t(this)))}}var ue=new de({_scriptable:t=>!t.startsWith("on"),_indexable:t=>"events"!==t,hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}},[function(t){t.set("animation",{delay:void 0,duration:1e3,easing:"easeOutQuart",fn:void 0,from:void 0,loop:void 0,to:void 0,type:void 0}),t.describe("animation",{_fallback:!1,_indexable:!1,_scriptable:t=>"onProgress"!==t&&"onComplete"!==t&&"fn"!==t}),t.set("animations",{colors:{type:"color",properties:ie},numbers:{type:"number",properties:ee}}),t.describe("animations",{_fallback:"animation"}),t.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:t=>0|t}}}})},function(t){t.set("layout",{autoPadding:!0,padding:{top:0,right:0,bottom:0,left:0}})},function(t){t.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",grace:0,grid:{display:!0,lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(t,e)=>e.lineWidth,tickColor:(t,e)=>e.color,offset:!1},border:{display:!0,dash:[],dashOffset:0,width:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:ae.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),t.route("scale.ticks","color","","color"),t.route("scale.grid","color","","borderColor"),t.route("scale.border","color","","borderColor"),t.route("scale.title","color","","color"),t.describe("scale",{_fallback:!1,_scriptable:t=>!t.startsWith("before")&&!t.startsWith("after")&&"callback"!==t&&"parser"!==t,_indexable:t=>"borderDash"!==t&&"tickBorderDash"!==t&&"dash"!==t}),t.describe("scales",{_fallback:"scale"}),t.describe("scale.ticks",{_scriptable:t=>"backdropPadding"!==t&&"callback"!==t,_indexable:t=>"backdropPadding"!==t})}]);function fe(){return"undefined"!=typeof window&&"undefined"!=typeof document}function ge(t){let e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e}function pe(t,e,i){let s;return"string"==typeof t?(s=parseInt(t,10),-1!==t.indexOf("%")&&(s=s/100*e.parentNode[i])):s=t,s}const me=t=>t.ownerDocument.defaultView.getComputedStyle(t,null);function be(t,e){return me(t).getPropertyValue(e)}const xe=["top","right","bottom","left"];function _e(t,e,i){const s={};i=i?"-"+i:"";for(let n=0;n<4;n++){const o=xe[n];s[o]=parseFloat(t[e+"-"+o+i])||0}return s.width=s.left+s.right,s.height=s.top+s.bottom,s}function ye(t,e){if("native"in t)return t;const{canvas:i,currentDevicePixelRatio:s}=e,n=me(i),o="border-box"===n.boxSizing,a=_e(n,"padding"),r=_e(n,"border","width"),{x:l,y:h,box:c}=function(t,e){const i=t.touches,s=i&&i.length?i[0]:t,{offsetX:n,offsetY:o}=s;let a,r,l=!1;if(((t,e,i)=>(t>0||e>0)&&(!i||!i.shadowRoot))(n,o,t.target))a=n,r=o;else{const t=e.getBoundingClientRect();a=s.clientX-t.left,r=s.clientY-t.top,l=!0}return{x:a,y:r,box:l}}(t,i),d=a.left+(c&&r.left),u=a.top+(c&&r.top);let{width:f,height:g}=e;return o&&(f-=a.width+r.width,g-=a.height+r.height),{x:Math.round((l-d)/f*i.width/s),y:Math.round((h-u)/g*i.height/s)}}const ve=t=>Math.round(10*t)/10;function Me(t,e,i,s){const n=me(t),o=_e(n,"margin"),a=pe(n.maxWidth,t,"clientWidth")||T,r=pe(n.maxHeight,t,"clientHeight")||T,l=function(t,e,i){let s,n;if(void 0===e||void 0===i){const o=ge(t);if(o){const t=o.getBoundingClientRect(),a=me(o),r=_e(a,"border","width"),l=_e(a,"padding");e=t.width-l.width-r.width,i=t.height-l.height-r.height,s=pe(a.maxWidth,o,"clientWidth"),n=pe(a.maxHeight,o,"clientHeight")}else e=t.clientWidth,i=t.clientHeight}return{width:e,height:i,maxWidth:s||T,maxHeight:n||T}}(t,e,i);let{width:h,height:c}=l;if("content-box"===n.boxSizing){const t=_e(n,"border","width"),e=_e(n,"padding");h-=e.width+t.width,c-=e.height+t.height}h=Math.max(0,h-o.width),c=Math.max(0,s?h/s:c-o.height),h=ve(Math.min(h,a,l.maxWidth)),c=ve(Math.min(c,r,l.maxHeight)),h&&!c&&(c=ve(h/2));return(void 0!==e||void 0!==i)&&s&&l.height&&c>l.height&&(c=l.height,h=ve(Math.floor(c*s))),{width:h,height:c}}function we(t,e,i){const s=e||1,n=Math.floor(t.height*s),o=Math.floor(t.width*s);t.height=Math.floor(t.height),t.width=Math.floor(t.width);const a=t.canvas;return a.style&&(i||!a.style.height&&!a.style.width)&&(a.style.height=`${t.height}px`,a.style.width=`${t.width}px`),(t.currentDevicePixelRatio!==s||a.height!==n||a.width!==o)&&(t.currentDevicePixelRatio=s,a.height=n,a.width=o,t.ctx.setTransform(s,0,0,s,0,0),!0)}const ke=function(){let t=!1;try{const e={get passive(){return t=!0,!1}};window.addEventListener("test",null,e),window.removeEventListener("test",null,e)}catch(t){}return t}();function Se(t,e){const i=be(t,e),s=i&&i.match(/^(\d+)(\.\d+)?px$/);return s?+s[1]:void 0}function Pe(t){return!t||s(t.size)||s(t.family)?null:(t.style?t.style+" ":"")+(t.weight?t.weight+" ":"")+t.size+"px "+t.family}function De(t,e,i,s,n){let o=e[n];return o||(o=e[n]=t.measureText(n).width,i.push(n)),o>s&&(s=o),s}function Ce(t,e,i,s){let o=(s=s||{}).data=s.data||{},a=s.garbageCollect=s.garbageCollect||[];s.font!==e&&(o=s.data={},a=s.garbageCollect=[],s.font=e),t.save(),t.font=e;let r=0;const l=i.length;let h,c,d,u,f;for(h=0;hi.length){for(h=0;h0&&t.stroke()}}function Ee(t,e,i){return i=i||.5,!e||t&&t.x>e.left-i&&t.xe.top-i&&t.y0&&""!==r.strokeColor;let c,d;for(t.save(),t.font=a.string,function(t,e){e.translation&&t.translate(e.translation[0],e.translation[1]);s(e.rotation)||t.rotate(e.rotation);e.color&&(t.fillStyle=e.color);e.textAlign&&(t.textAlign=e.textAlign);e.textBaseline&&(t.textBaseline=e.textBaseline)}(t,r),c=0;ct[0])){k(s)||(s=Qe("_fallback",t));const o={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:t,_rootScopes:i,_fallback:s,_getTarget:n,override:n=>He([n,...t],e,i,s)};return new Proxy(o,{deleteProperty:(e,i)=>(delete e[i],delete e._keys,delete t[0][i],!0),get:(i,s)=>Xe(i,s,(()=>function(t,e,i,s){let n;for(const o of e)if(n=Qe(Ye(o,t),i),k(n))return Ue(t,n)?Ze(i,s,t,n):n}(s,e,t,i))),getOwnPropertyDescriptor:(t,e)=>Reflect.getOwnPropertyDescriptor(t._scopes[0],e),getPrototypeOf:()=>Reflect.getPrototypeOf(t[0]),has:(t,e)=>ti(t).includes(e),ownKeys:t=>ti(t),set(t,e,i){const s=t._storage||(t._storage=n());return t[e]=s[e]=i,delete t._keys,!0}})}function je(t,e,i,s){const a={_cacheable:!1,_proxy:t,_context:e,_subProxy:i,_stack:new Set,_descriptors:$e(t,s),setContext:e=>je(t,e,i,s),override:n=>je(t.override(n),e,i,s)};return new Proxy(a,{deleteProperty:(e,i)=>(delete e[i],delete t[i],!0),get:(t,e,i)=>Xe(t,e,(()=>function(t,e,i){const{_proxy:s,_context:a,_subProxy:r,_descriptors:l}=t;let h=s[e];S(h)&&l.isScriptable(e)&&(h=function(t,e,i,s){const{_proxy:n,_context:o,_subProxy:a,_stack:r}=i;if(r.has(t))throw new Error("Recursion detected: "+Array.from(r).join("->")+"->"+t);r.add(t),e=e(o,a||s),r.delete(t),Ue(t,e)&&(e=Ze(n._scopes,n,t,e));return e}(e,h,t,i));n(h)&&h.length&&(h=function(t,e,i,s){const{_proxy:n,_context:a,_subProxy:r,_descriptors:l}=i;if(k(a.index)&&s(t))e=e[a.index%e.length];else if(o(e[0])){const i=e,s=n._scopes.filter((t=>t!==i));e=[];for(const o of i){const i=Ze(s,n,t,o);e.push(je(i,a,r&&r[t],l))}}return e}(e,h,t,l.isIndexable));Ue(e,h)&&(h=je(h,a,r&&r[e],l));return h}(t,e,i))),getOwnPropertyDescriptor:(e,i)=>e._descriptors.allKeys?Reflect.has(t,i)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(t,i),getPrototypeOf:()=>Reflect.getPrototypeOf(t),has:(e,i)=>Reflect.has(t,i),ownKeys:()=>Reflect.ownKeys(t),set:(e,i,s)=>(t[i]=s,delete e[i],!0)})}function $e(t,e={scriptable:!0,indexable:!0}){const{_scriptable:i=e.scriptable,_indexable:s=e.indexable,_allKeys:n=e.allKeys}=t;return{allKeys:n,scriptable:i,indexable:s,isScriptable:S(i)?i:()=>i,isIndexable:S(s)?s:()=>s}}const Ye=(t,e)=>t?t+w(e):e,Ue=(t,e)=>o(e)&&"adapters"!==t&&(null===Object.getPrototypeOf(e)||e.constructor===Object);function Xe(t,e,i){if(Object.prototype.hasOwnProperty.call(t,e))return t[e];const s=i();return t[e]=s,s}function qe(t,e,i){return S(t)?t(e,i):t}const Ke=(t,e)=>!0===t?e:"string"==typeof t?M(e,t):void 0;function Ge(t,e,i,s,n){for(const o of e){const e=Ke(i,o);if(e){t.add(e);const o=qe(e._fallback,i,n);if(k(o)&&o!==i&&o!==s)return o}else if(!1===e&&k(s)&&i!==s)return null}return!1}function Ze(t,e,i,s){const a=e._rootScopes,r=qe(e._fallback,i,s),l=[...t,...a],h=new Set;h.add(s);let c=Je(h,l,i,r||i,s);return null!==c&&((!k(r)||r===i||(c=Je(h,l,r,c,s),null!==c))&&He(Array.from(h),[""],a,r,(()=>function(t,e,i){const s=t._getTarget();e in s||(s[e]={});const a=s[e];if(n(a)&&o(i))return i;return a||{}}(e,i,s))))}function Je(t,e,i,s,n){for(;i;)i=Ge(t,e,i,s,n);return i}function Qe(t,e){for(const i of e){if(!i)continue;const e=i[t];if(k(e))return e}}function ti(t){let e=t._keys;return e||(e=t._keys=function(t){const e=new Set;for(const i of t)for(const t of Object.keys(i).filter((t=>!t.startsWith("_"))))e.add(t);return Array.from(e)}(t._scopes)),e}function ei(t,e,i,s){const{iScale:n}=t,{key:o="r"}=this._parsing,a=new Array(s);let r,l,h,c;for(r=0,l=s;re"x"===t?"y":"x";function oi(t,e,i,s){const n=t.skip?e:t,o=e,a=i.skip?e:i,r=q(o,n),l=q(a,o);let h=r/(r+l),c=l/(r+l);h=isNaN(h)?0:h,c=isNaN(c)?0:c;const d=s*h,u=s*c;return{previous:{x:o.x-d*(a.x-n.x),y:o.y-d*(a.y-n.y)},next:{x:o.x+u*(a.x-n.x),y:o.y+u*(a.y-n.y)}}}function ai(t,e="x"){const i=ni(e),s=t.length,n=Array(s).fill(0),o=Array(s);let a,r,l,h=si(t,0);for(a=0;a!t.skip))),"monotone"===e.cubicInterpolationMode)ai(t,n);else{let i=s?t[t.length-1]:t[0];for(o=0,a=t.length;o0===t||1===t,ci=(t,e,i)=>-Math.pow(2,10*(t-=1))*Math.sin((t-e)*O/i),di=(t,e,i)=>Math.pow(2,-10*t)*Math.sin((t-e)*O/i)+1,ui={linear:t=>t,easeInQuad:t=>t*t,easeOutQuad:t=>-t*(t-2),easeInOutQuad:t=>(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1),easeInCubic:t=>t*t*t,easeOutCubic:t=>(t-=1)*t*t+1,easeInOutCubic:t=>(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2),easeInQuart:t=>t*t*t*t,easeOutQuart:t=>-((t-=1)*t*t*t-1),easeInOutQuart:t=>(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2),easeInQuint:t=>t*t*t*t*t,easeOutQuint:t=>(t-=1)*t*t*t*t+1,easeInOutQuint:t=>(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2),easeInSine:t=>1-Math.cos(t*E),easeOutSine:t=>Math.sin(t*E),easeInOutSine:t=>-.5*(Math.cos(C*t)-1),easeInExpo:t=>0===t?0:Math.pow(2,10*(t-1)),easeOutExpo:t=>1===t?1:1-Math.pow(2,-10*t),easeInOutExpo:t=>hi(t)?t:t<.5?.5*Math.pow(2,10*(2*t-1)):.5*(2-Math.pow(2,-10*(2*t-1))),easeInCirc:t=>t>=1?t:-(Math.sqrt(1-t*t)-1),easeOutCirc:t=>Math.sqrt(1-(t-=1)*t),easeInOutCirc:t=>(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1),easeInElastic:t=>hi(t)?t:ci(t,.075,.3),easeOutElastic:t=>hi(t)?t:di(t,.075,.3),easeInOutElastic(t){const e=.1125;return hi(t)?t:t<.5?.5*ci(2*t,e,.45):.5+.5*di(2*t-1,e,.45)},easeInBack(t){const e=1.70158;return t*t*((e+1)*t-e)},easeOutBack(t){const e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack(t){let e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:t=>1-ui.easeOutBounce(1-t),easeOutBounce(t){const e=7.5625,i=2.75;return t<1/i?e*t*t:t<2/i?e*(t-=1.5/i)*t+.75:t<2.5/i?e*(t-=2.25/i)*t+.9375:e*(t-=2.625/i)*t+.984375},easeInOutBounce:t=>t<.5?.5*ui.easeInBounce(2*t):.5*ui.easeOutBounce(2*t-1)+.5};function fi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:t.y+i*(e.y-t.y)}}function gi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:"middle"===s?i<.5?t.y:e.y:"after"===s?i<1?t.y:e.y:i>0?e.y:t.y}}function pi(t,e,i,s){const n={x:t.cp2x,y:t.cp2y},o={x:e.cp1x,y:e.cp1y},a=fi(t,n,i),r=fi(n,o,i),l=fi(o,e,i),h=fi(a,r,i),c=fi(r,l,i);return fi(h,c,i)}const mi=/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/,bi=/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/;function xi(t,e){const i=(""+t).match(mi);if(!i||"normal"===i[1])return 1.2*e;switch(t=+i[2],i[3]){case"px":return t;case"%":t/=100}return e*t}function _i(t,e){const i={},s=o(e),n=s?Object.keys(e):e,a=o(t)?s?i=>l(t[i],t[e[i]]):e=>t[e]:()=>t;for(const t of n)i[t]=+a(t)||0;return i}function yi(t){return _i(t,{top:"y",right:"x",bottom:"y",left:"x"})}function vi(t){return _i(t,["topLeft","topRight","bottomLeft","bottomRight"])}function Mi(t){const e=yi(t);return e.width=e.left+e.right,e.height=e.top+e.bottom,e}function wi(t,e){t=t||{},e=e||ue.font;let i=l(t.size,e.size);"string"==typeof i&&(i=parseInt(i,10));let s=l(t.style,e.style);s&&!(""+s).match(bi)&&(console.warn('Invalid font style specified: "'+s+'"'),s=void 0);const n={family:l(t.family,e.family),lineHeight:xi(l(t.lineHeight,e.lineHeight),i),size:i,style:s,weight:l(t.weight,e.weight),string:""};return n.string=Pe(n),n}function ki(t,e,i,s){let o,a,r,l=!0;for(o=0,a=t.length;oi&&0===t?0:t+e;return{min:a(s,-Math.abs(o)),max:a(n,o)}}function Pi(t,e){return Object.assign(Object.create(t),e)}function Di(t,e,i){return t?function(t,e){return{x:i=>t+t+e-i,setWidth(t){e=t},textAlign:t=>"center"===t?t:"right"===t?"left":"right",xPlus:(t,e)=>t-e,leftForLtr:(t,e)=>t-e}}(e,i):{x:t=>t,setWidth(t){},textAlign:t=>t,xPlus:(t,e)=>t+e,leftForLtr:(t,e)=>t}}function Ci(t,e){let i,s;"ltr"!==e&&"rtl"!==e||(i=t.canvas.style,s=[i.getPropertyValue("direction"),i.getPropertyPriority("direction")],i.setProperty("direction",e,"important"),t.prevTextDirection=s)}function Oi(t,e){void 0!==e&&(delete t.prevTextDirection,t.canvas.style.setProperty("direction",e[0],e[1]))}function Ai(t){return"angle"===t?{between:Z,compare:K,normalize:G}:{between:tt,compare:(t,e)=>t-e,normalize:t=>t}}function Ti({start:t,end:e,count:i,loop:s,style:n}){return{start:t%i,end:e%i,loop:s&&(e-t+1)%i==0,style:n}}function Li(t,e,i){if(!i)return[t];const{property:s,start:n,end:o}=i,a=e.length,{compare:r,between:l,normalize:h}=Ai(s),{start:c,end:d,loop:u,style:f}=function(t,e,i){const{property:s,start:n,end:o}=i,{between:a,normalize:r}=Ai(s),l=e.length;let h,c,{start:d,end:u,loop:f}=t;if(f){for(d+=l,u+=l,h=0,c=l;hx||l(n,b,p)&&0!==r(n,b),v=()=>!x||0===r(o,p)||l(o,b,p);for(let t=c,i=c;t<=d;++t)m=e[t%a],m.skip||(p=h(m[s]),p!==b&&(x=l(p,n,o),null===_&&y()&&(_=0===r(p,n)?t:i),null!==_&&v()&&(g.push(Ti({start:_,end:t,loop:u,count:a,style:f})),_=null),i=t,b=p));return null!==_&&g.push(Ti({start:_,end:d,loop:u,count:a,style:f})),g}function Ei(t,e){const i=[],s=t.segments;for(let n=0;nn&&t[o%e].skip;)o--;return o%=e,{start:n,end:o}}(i,n,o,s);if(!0===s)return Ii(t,[{start:a,end:r,loop:o}],i,e);return Ii(t,function(t,e,i,s){const n=t.length,o=[];let a,r=e,l=t[e];for(a=e+1;a<=i;++a){const i=t[a%n];i.skip||i.stop?l.skip||(s=!1,o.push({start:e%n,end:(a-1)%n,loop:s}),e=r=i.stop?a:null):(r=a,l.skip&&(e=a)),l=i}return null!==r&&o.push({start:e%n,end:r%n,loop:s}),o}(i,a,r{t[a](e[i],n)&&(o.push({element:t,datasetIndex:s,index:l}),r=r||t.inRange(e.x,e.y,n))})),s&&!r?[]:o}var Yi={evaluateInteractionItems:Ni,modes:{index(t,e,i,s){const n=ye(e,t),o=i.axis||"x",a=i.includeInvisible||!1,r=i.intersect?Wi(t,n,o,s,a):ji(t,n,o,!1,s,a),l=[];return r.length?(t.getSortedVisibleDatasetMetas().forEach((t=>{const e=r[0].index,i=t.data[e];i&&!i.skip&&l.push({element:i,datasetIndex:t.index,index:e})})),l):[]},dataset(t,e,i,s){const n=ye(e,t),o=i.axis||"xy",a=i.includeInvisible||!1;let r=i.intersect?Wi(t,n,o,s,a):ji(t,n,o,!1,s,a);if(r.length>0){const e=r[0].datasetIndex,i=t.getDatasetMeta(e).data;r=[];for(let t=0;tWi(t,ye(e,t),i.axis||"xy",s,i.includeInvisible||!1),nearest(t,e,i,s){const n=ye(e,t),o=i.axis||"xy",a=i.includeInvisible||!1;return ji(t,n,o,i.intersect,s,a)},x:(t,e,i,s)=>$i(t,ye(e,t),"x",i.intersect,s),y:(t,e,i,s)=>$i(t,ye(e,t),"y",i.intersect,s)}};const Ui=["left","top","right","bottom"];function Xi(t,e){return t.filter((t=>t.pos===e))}function qi(t,e){return t.filter((t=>-1===Ui.indexOf(t.pos)&&t.box.axis===e))}function Ki(t,e){return t.sort(((t,i)=>{const s=e?i:t,n=e?t:i;return s.weight===n.weight?s.index-n.index:s.weight-n.weight}))}function Gi(t,e){const i=function(t){const e={};for(const i of t){const{stack:t,pos:s,stackWeight:n}=i;if(!t||!Ui.includes(s))continue;const o=e[t]||(e[t]={count:0,placed:0,weight:0,size:0});o.count++,o.weight+=n}return e}(t),{vBoxMaxWidth:s,hBoxMaxHeight:n}=e;let o,a,r;for(o=0,a=t.length;o{s[t]=Math.max(e[t],i[t])})),s}return s(t?["left","right"]:["top","bottom"])}function es(t,e,i,s){const n=[];let o,a,r,l,h,c;for(o=0,a=t.length,h=0;ot.box.fullSize)),!0),s=Ki(Xi(e,"left"),!0),n=Ki(Xi(e,"right")),o=Ki(Xi(e,"top"),!0),a=Ki(Xi(e,"bottom")),r=qi(e,"x"),l=qi(e,"y");return{fullSize:i,leftAndTop:s.concat(o),rightAndBottom:n.concat(l).concat(a).concat(r),chartArea:Xi(e,"chartArea"),vertical:s.concat(n).concat(l),horizontal:o.concat(a).concat(r)}}(t.boxes),l=r.vertical,h=r.horizontal;u(t.boxes,(t=>{"function"==typeof t.beforeLayout&&t.beforeLayout()}));const c=l.reduce(((t,e)=>e.box.options&&!1===e.box.options.display?t:t+1),0)||1,d=Object.freeze({outerWidth:e,outerHeight:i,padding:n,availableWidth:o,availableHeight:a,vBoxMaxWidth:o/2/c,hBoxMaxHeight:a/2}),f=Object.assign({},n);Ji(f,Mi(s));const g=Object.assign({maxPadding:f,w:o,h:a,x:n.left,y:n.top},n),p=Gi(l.concat(h),d);es(r.fullSize,g,d,p),es(l,g,d,p),es(h,g,d,p)&&es(l,g,d,p),function(t){const e=t.maxPadding;function i(i){const s=Math.max(e[i]-t[i],0);return t[i]+=s,s}t.y+=i("top"),t.x+=i("left"),i("right"),i("bottom")}(g),ss(r.leftAndTop,g,d,p),g.x+=g.w,g.y+=g.h,ss(r.rightAndBottom,g,d,p),t.chartArea={left:g.left,top:g.top,right:g.left+g.w,bottom:g.top+g.h,height:g.h,width:g.w},u(r.chartArea,(e=>{const i=e.box;Object.assign(i,t.chartArea),i.update(g.w,g.h,{left:0,top:0,right:0,bottom:0})}))}};class os{acquireContext(t,e){}releaseContext(t){return!1}addEventListener(t,e,i){}removeEventListener(t,e,i){}getDevicePixelRatio(){return 1}getMaximumSize(t,e,i,s){return e=Math.max(0,e||t.width),i=i||t.height,{width:e,height:Math.max(0,s?Math.floor(e/s):i)}}isAttached(t){return!0}updateConfig(t){}}class as extends os{acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}updateConfig(t){t.options.animation=!1}}const rs={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},ls=t=>null===t||""===t;const hs=!!ke&&{passive:!0};function cs(t,e,i){t.canvas.removeEventListener(e,i,hs)}function ds(t,e){for(const i of t)if(i===e||i.contains(e))return!0}function us(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||ds(i.addedNodes,s),e=e&&!ds(i.removedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}function fs(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||ds(i.removedNodes,s),e=e&&!ds(i.addedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}const gs=new Map;let ps=0;function ms(){const t=window.devicePixelRatio;t!==ps&&(ps=t,gs.forEach(((e,i)=>{i.currentDevicePixelRatio!==t&&e()})))}function bs(t,e,i){const s=t.canvas,n=s&&ge(s);if(!n)return;const o=ct(((t,e)=>{const s=n.clientWidth;i(t,e),s{const e=t[0],i=e.contentRect.width,s=e.contentRect.height;0===i&&0===s||o(i,s)}));return a.observe(n),function(t,e){gs.size||window.addEventListener("resize",ms),gs.set(t,e)}(t,o),a}function xs(t,e,i){i&&i.disconnect(),"resize"===e&&function(t){gs.delete(t),gs.size||window.removeEventListener("resize",ms)}(t)}function _s(t,e,i){const s=t.canvas,n=ct((e=>{null!==t.ctx&&i(function(t,e){const i=rs[t.type]||t.type,{x:s,y:n}=ye(t,e);return{type:i,chart:e,native:t,x:void 0!==s?s:null,y:void 0!==n?n:null}}(e,t))}),t);return function(t,e,i){t.addEventListener(e,i,hs)}(s,e,n),n}class ys extends os{acquireContext(t,e){const i=t&&t.getContext&&t.getContext("2d");return i&&i.canvas===t?(function(t,e){const i=t.style,s=t.getAttribute("height"),n=t.getAttribute("width");if(t.$chartjs={initial:{height:s,width:n,style:{display:i.display,height:i.height,width:i.width}}},i.display=i.display||"block",i.boxSizing=i.boxSizing||"border-box",ls(n)){const e=Se(t,"width");void 0!==e&&(t.width=e)}if(ls(s))if(""===t.style.height)t.height=t.width/(e||2);else{const e=Se(t,"height");void 0!==e&&(t.height=e)}}(t,e),i):null}releaseContext(t){const e=t.canvas;if(!e.$chartjs)return!1;const i=e.$chartjs.initial;["height","width"].forEach((t=>{const n=i[t];s(n)?e.removeAttribute(t):e.setAttribute(t,n)}));const n=i.style||{};return Object.keys(n).forEach((t=>{e.style[t]=n[t]})),e.width=e.width,delete e.$chartjs,!0}addEventListener(t,e,i){this.removeEventListener(t,e);const s=t.$proxies||(t.$proxies={}),n={attach:us,detach:fs,resize:bs}[e]||_s;s[e]=n(t,e,i)}removeEventListener(t,e){const i=t.$proxies||(t.$proxies={}),s=i[e];if(!s)return;({attach:xs,detach:xs,resize:xs}[e]||cs)(t,e,s),i[e]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,e,i,s){return Me(t,e,i,s)}isAttached(t){const e=ge(t);return!(!e||!e.isConnected)}}function vs(t){return!fe()||"undefined"!=typeof OffscreenCanvas&&t instanceof OffscreenCanvas?as:ys}var Ms=Object.freeze({__proto__:null,_detectPlatform:vs,BasePlatform:os,BasicPlatform:as,DomPlatform:ys});const ws="transparent",ks={boolean:(t,e,i)=>i>.5?e:t,color(t,e,i){const s=Qt(t||ws),n=s.valid&&Qt(e||ws);return n&&n.valid?n.mix(s,i).hexString():e},number:(t,e,i)=>t+(e-t)*i};class Ss{constructor(t,e,i,s){const n=e[i];s=ki([t.to,s,n,t.from]);const o=ki([t.from,n,s]);this._active=!0,this._fn=t.fn||ks[t.type||typeof o],this._easing=ui[t.easing]||ui.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=e,this._prop=i,this._from=o,this._to=s,this._promises=void 0}active(){return this._active}update(t,e,i){if(this._active){this._notify(!1);const s=this._target[this._prop],n=i-this._start,o=this._duration-n;this._start=i,this._duration=Math.floor(Math.max(o,t.duration)),this._total+=n,this._loop=!!t.loop,this._to=ki([t.to,e,s,t.from]),this._from=ki([t.from,s,e])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(t){const e=t-this._start,i=this._duration,s=this._prop,n=this._from,o=this._loop,a=this._to;let r;if(this._active=n!==a&&(o||e1?2-r:r,r=this._easing(Math.min(1,Math.max(0,r))),this._target[s]=this._fn(n,a,r))}wait(){const t=this._promises||(this._promises=[]);return new Promise(((e,i)=>{t.push({res:e,rej:i})}))}_notify(t){const e=t?"res":"rej",i=this._promises||[];for(let t=0;t{const a=t[s];if(!o(a))return;const r={};for(const t of e)r[t]=a[t];(n(a.properties)&&a.properties||[s]).forEach((t=>{t!==s&&i.has(t)||i.set(t,r)}))}))}_animateOptions(t,e){const i=e.options,s=function(t,e){if(!e)return;let i=t.options;if(!i)return void(t.options=e);i.$shared&&(t.options=i=Object.assign({},i,{$shared:!1,$animations:{}}));return i}(t,i);if(!s)return[];const n=this._createAnimations(s,i);return i.$shared&&function(t,e){const i=[],s=Object.keys(e);for(let e=0;e{t.options=i}),(()=>{})),n}_createAnimations(t,e){const i=this._properties,s=[],n=t.$animations||(t.$animations={}),o=Object.keys(e),a=Date.now();let r;for(r=o.length-1;r>=0;--r){const l=o[r];if("$"===l.charAt(0))continue;if("options"===l){s.push(...this._animateOptions(t,e));continue}const h=e[l];let c=n[l];const d=i.get(l);if(c){if(d&&c.active()){c.update(d,h,a);continue}c.cancel()}d&&d.duration?(n[l]=c=new Ss(d,t,l,h),s.push(c)):t[l]=h}return s}update(t,e){if(0===this._properties.size)return void Object.assign(t,e);const i=this._createAnimations(t,e);return i.length?(xt.add(this._chart,i),!0):void 0}}function Ds(t,e){const i=t&&t.options||{},s=i.reverse,n=void 0===i.min?e:0,o=void 0===i.max?e:0;return{start:s?o:n,end:s?n:o}}function Cs(t,e){const i=[],s=t._getSortedDatasetMetas(e);let n,o;for(n=0,o=s.length;n0||!i&&e<0)return n.index}return null}function Es(t,e){const{chart:i,_cachedMeta:s}=t,n=i._stacks||(i._stacks={}),{iScale:o,vScale:a,index:r}=s,l=o.axis,h=a.axis,c=function(t,e,i){return`${t.id}.${e.id}.${i.stack||i.type}`}(o,a,s),d=e.length;let u;for(let t=0;ti[t].axis===e)).shift()}function Is(t,e){const i=t.controller.index,s=t.vScale&&t.vScale.axis;if(s){e=e||t._parsed;for(const t of e){const e=t._stacks;if(!e||void 0===e[s]||void 0===e[s][i])return;delete e[s][i],void 0!==e[s]._visualValues&&void 0!==e[s]._visualValues[i]&&delete e[s]._visualValues[i]}}}const zs=t=>"reset"===t||"none"===t,Fs=(t,e)=>e?t:Object.assign({},t);class Vs{static defaults={};static datasetElementType=null;static dataElementType=null;constructor(t,e){this.chart=t,this._ctx=t.ctx,this.index=e,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.datasetElementType=new.target.datasetElementType,this.dataElementType=new.target.dataElementType,this.initialize()}initialize(){const t=this._cachedMeta;this.configure(),this.linkScales(),t._stacked=As(t.vScale,t),this.addElements(),this.options.fill&&!this.chart.isPluginEnabled("filler")&&console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options")}updateIndex(t){this.index!==t&&Is(this._cachedMeta),this.index=t}linkScales(){const t=this.chart,e=this._cachedMeta,i=this.getDataset(),s=(t,e,i,s)=>"x"===t?e:"r"===t?s:i,n=e.xAxisID=l(i.xAxisID,Rs(t,"x")),o=e.yAxisID=l(i.yAxisID,Rs(t,"y")),a=e.rAxisID=l(i.rAxisID,Rs(t,"r")),r=e.indexAxis,h=e.iAxisID=s(r,n,o,a),c=e.vAxisID=s(r,o,n,a);e.xScale=this.getScaleForId(n),e.yScale=this.getScaleForId(o),e.rScale=this.getScaleForId(a),e.iScale=this.getScaleForId(h),e.vScale=this.getScaleForId(c)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){const e=this._cachedMeta;return t===e.iScale?e.vScale:e.iScale}reset(){this._update("reset")}_destroy(){const t=this._cachedMeta;this._data&&rt(this._data,this),t._stacked&&Is(t)}_dataCheck(){const t=this.getDataset(),e=t.data||(t.data=[]),i=this._data;if(o(e))this._data=function(t){const e=Object.keys(t),i=new Array(e.length);let s,n,o;for(s=0,n=e.length;s0&&i._parsed[t-1];if(!1===this._parsing)i._parsed=s,i._sorted=!0,d=s;else{d=n(s[t])?this.parseArrayData(i,s,t,e):o(s[t])?this.parseObjectData(i,s,t,e):this.parsePrimitiveData(i,s,t,e);const a=()=>null===c[l]||f&&c[l]t&&!e.hidden&&e._stacked&&{keys:Cs(i,!0),values:null})(e,i,this.chart),h={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY},{min:c,max:d}=function(t){const{min:e,max:i,minDefined:s,maxDefined:n}=t.getUserBounds();return{min:s?e:Number.NEGATIVE_INFINITY,max:n?i:Number.POSITIVE_INFINITY}}(r);let u,f;function g(){f=s[u];const e=f[r.axis];return!a(f[t.axis])||c>e||d=0;--u)if(!g()){this.updateRangeFromParsed(h,t,f,l);break}return h}getAllParsedValues(t){const e=this._cachedMeta._parsed,i=[];let s,n,o;for(s=0,n=e.length;s=0&&tthis.getContext(i,s,e)),c);return f.$shared&&(f.$shared=r,n[o]=Object.freeze(Fs(f,r))),f}_resolveAnimations(t,e,i){const s=this.chart,n=this._cachedDataOpts,o=`animation-${e}`,a=n[o];if(a)return a;let r;if(!1!==s.options.animation){const s=this.chart.config,n=s.datasetAnimationScopeKeys(this._type,e),o=s.getOptionScopes(this.getDataset(),n);r=s.createResolver(o,this.getContext(t,i,e))}const l=new Ps(s,r&&r.animations);return r&&r._cacheable&&(n[o]=Object.freeze(l)),l}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,e){return!e||zs(t)||this.chart._animationsDisabled}_getSharedOptions(t,e){const i=this.resolveDataElementOptions(t,e),s=this._sharedOptions,n=this.getSharedOptions(i),o=this.includeOptions(e,n)||n!==s;return this.updateSharedOptions(n,e,i),{sharedOptions:n,includeOptions:o}}updateElement(t,e,i,s){zs(s)?Object.assign(t,i):this._resolveAnimations(e,s).update(t,i)}updateSharedOptions(t,e,i){t&&!zs(e)&&this._resolveAnimations(void 0,e).update(t,i)}_setStyle(t,e,i,s){t.active=s;const n=this.getStyle(e,s);this._resolveAnimations(e,i,s).update(t,{options:!s&&this.getSharedOptions(n)||n})}removeHoverStyle(t,e,i){this._setStyle(t,i,"active",!1)}setHoverStyle(t,e,i){this._setStyle(t,i,"active",!0)}_removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){const e=this._data,i=this._cachedMeta.data;for(const[t,e,i]of this._syncList)this[t](e,i);this._syncList=[];const s=i.length,n=e.length,o=Math.min(n,s);o&&this.parse(0,o),n>s?this._insertElements(s,n-s,t):n{for(t.length+=e,a=t.length-1;a>=o;a--)t[a]=t[a-e]};for(r(n),a=t;a{s[t]=i[t]&&i[t].active()?i[t]._to:this[t]})),s}}function Ns(t,e){const i=t.options.ticks,n=function(t){const e=t.options.offset,i=t._tickSize(),s=t._length/i+(e?0:1),n=t._maxLength/i;return Math.floor(Math.min(s,n))}(t),o=Math.min(i.maxTicksLimit||n,n),a=i.major.enabled?function(t){const e=[];let i,s;for(i=0,s=t.length;io)return function(t,e,i,s){let n,o=0,a=i[0];for(s=Math.ceil(s),n=0;nn)return e}return Math.max(n,1)}(a,e,o);if(r>0){let t,i;const n=r>1?Math.round((h-l)/(r-1)):null;for(Ws(e,c,d,s(n)?0:l-n,l),t=0,i=r-1;t"top"===e||"left"===e?t[e]+i:t[e]-i,js=(t,e)=>Math.min(e||t,t);function $s(t,e){const i=[],s=t.length/e,n=t.length;let o=0;for(;oa+r)))return h}function Us(t){return t.drawTicks?t.tickLength:0}function Xs(t,e){if(!t.display)return 0;const i=wi(t.font,e),s=Mi(t.padding);return(n(t.text)?t.text.length:1)*i.lineHeight+s.height}function qs(t,e,i){let s=ut(t);return(i&&"right"!==e||!i&&"right"===e)&&(s=(t=>"left"===t?"right":"right"===t?"left":t)(s)),s}class Ks extends Bs{constructor(t){super(),this.id=t.id,this.type=t.type,this.options=void 0,this.ctx=t.ctx,this.chart=t.chart,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this._margins={left:0,right:0,top:0,bottom:0},this.maxWidth=void 0,this.maxHeight=void 0,this.paddingTop=void 0,this.paddingBottom=void 0,this.paddingLeft=void 0,this.paddingRight=void 0,this.axis=void 0,this.labelRotation=void 0,this.min=void 0,this.max=void 0,this._range=void 0,this.ticks=[],this._gridLineItems=null,this._labelItems=null,this._labelSizes=null,this._length=0,this._maxLength=0,this._longestTextCache={},this._startPixel=void 0,this._endPixel=void 0,this._reversePixels=!1,this._userMax=void 0,this._userMin=void 0,this._suggestedMax=void 0,this._suggestedMin=void 0,this._ticksLength=0,this._borderValue=0,this._cache={},this._dataLimitsCached=!1,this.$context=void 0}init(t){this.options=t.setContext(this.getContext()),this.axis=t.axis,this._userMin=this.parse(t.min),this._userMax=this.parse(t.max),this._suggestedMin=this.parse(t.suggestedMin),this._suggestedMax=this.parse(t.suggestedMax)}parse(t,e){return t}getUserBounds(){let{_userMin:t,_userMax:e,_suggestedMin:i,_suggestedMax:s}=this;return t=r(t,Number.POSITIVE_INFINITY),e=r(e,Number.NEGATIVE_INFINITY),i=r(i,Number.POSITIVE_INFINITY),s=r(s,Number.NEGATIVE_INFINITY),{min:r(t,i),max:r(e,s),minDefined:a(t),maxDefined:a(e)}}getMinMax(t){let e,{min:i,max:s,minDefined:n,maxDefined:o}=this.getUserBounds();if(n&&o)return{min:i,max:s};const a=this.getMatchingVisibleMetas();for(let r=0,l=a.length;rs?s:i,s=n&&i>s?i:s,{min:r(i,r(s,i)),max:r(s,r(i,s))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){const t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]}getLabelItems(t=this.chart.chartArea){return this._labelItems||(this._labelItems=this._computeLabelItems(t))}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){d(this.options.beforeUpdate,[this])}update(t,e,i){const{beginAtZero:s,grace:n,ticks:o}=this.options,a=o.sampleSize;this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this._margins=i=Object.assign({left:0,right:0,top:0,bottom:0},i),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+i.left+i.right:this.height+i.top+i.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=Si(this,n,s),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();const r=a=n||i<=1||!this.isHorizontal())return void(this.labelRotation=s);const h=this._getLabelSizes(),c=h.widest.width,d=h.highest.height,u=J(this.chart.width-c,0,this.maxWidth);o=t.offset?this.maxWidth/i:u/(i-1),c+6>o&&(o=u/(i-(t.offset?.5:1)),a=this.maxHeight-Us(t.grid)-e.padding-Xs(t.title,this.chart.options.font),r=Math.sqrt(c*c+d*d),l=Y(Math.min(Math.asin(J((h.highest.height+6)/o,-1,1)),Math.asin(J(a/r,-1,1))-Math.asin(J(d/r,-1,1)))),l=Math.max(s,Math.min(n,l))),this.labelRotation=l}afterCalculateLabelRotation(){d(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){d(this.options.beforeFit,[this])}fit(){const t={width:0,height:0},{chart:e,options:{ticks:i,title:s,grid:n}}=this,o=this._isVisible(),a=this.isHorizontal();if(o){const o=Xs(s,e.options.font);if(a?(t.width=this.maxWidth,t.height=Us(n)+o):(t.height=this.maxHeight,t.width=Us(n)+o),i.display&&this.ticks.length){const{first:e,last:s,widest:n,highest:o}=this._getLabelSizes(),r=2*i.padding,l=$(this.labelRotation),h=Math.cos(l),c=Math.sin(l);if(a){const e=i.mirror?0:c*n.width+h*o.height;t.height=Math.min(this.maxHeight,t.height+e+r)}else{const e=i.mirror?0:h*n.width+c*o.height;t.width=Math.min(this.maxWidth,t.width+e+r)}this._calculatePadding(e,s,c,h)}}this._handleMargins(),a?(this.width=this._length=e.width-this._margins.left-this._margins.right,this.height=t.height):(this.width=t.width,this.height=this._length=e.height-this._margins.top-this._margins.bottom)}_calculatePadding(t,e,i,s){const{ticks:{align:n,padding:o},position:a}=this.options,r=0!==this.labelRotation,l="top"!==a&&"x"===this.axis;if(this.isHorizontal()){const a=this.getPixelForTick(0)-this.left,h=this.right-this.getPixelForTick(this.ticks.length-1);let c=0,d=0;r?l?(c=s*t.width,d=i*e.height):(c=i*t.height,d=s*e.width):"start"===n?d=e.width:"end"===n?c=t.width:"inner"!==n&&(c=t.width/2,d=e.width/2),this.paddingLeft=Math.max((c-a+o)*this.width/(this.width-a),0),this.paddingRight=Math.max((d-h+o)*this.width/(this.width-h),0)}else{let i=e.height/2,s=t.height/2;"start"===n?(i=0,s=t.height):"end"===n&&(i=e.height,s=0),this.paddingTop=i+o,this.paddingBottom=s+o}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){d(this.options.afterFit,[this])}isHorizontal(){const{axis:t,position:e}=this.options;return"top"===e||"bottom"===e||"x"===t}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){let e,i;for(this.beforeTickToLabelConversion(),this.generateTickLabels(t),e=0,i=t.length;e{const i=t.gc,s=i.length/2;let n;if(s>e){for(n=0;n({width:r[t]||0,height:l[t]||0});return{first:P(0),last:P(e-1),widest:P(k),highest:P(S),widths:r,heights:l}}getLabelForValue(t){return t}getPixelForValue(t,e){return NaN}getValueForPixel(t){}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getPixelForDecimal(t){this._reversePixels&&(t=1-t);const e=this._startPixel+t*this._length;return Q(this._alignToPixels?Oe(this.chart,e,0):e)}getDecimalForPixel(t){const e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:t,max:e}=this;return t<0&&e<0?e:t>0&&e>0?t:0}getContext(t){const e=this.ticks||[];if(t>=0&&ta*s?a/i:r/s:r*s0}_computeGridLineItems(t){const e=this.axis,i=this.chart,s=this.options,{grid:n,position:a,border:r}=s,h=n.offset,c=this.isHorizontal(),d=this.ticks.length+(h?1:0),u=Us(n),f=[],g=r.setContext(this.getContext()),p=g.display?g.width:0,m=p/2,b=function(t){return Oe(i,t,p)};let x,_,y,v,M,w,k,S,P,D,C,O;if("top"===a)x=b(this.bottom),w=this.bottom-u,S=x-m,D=b(t.top)+m,O=t.bottom;else if("bottom"===a)x=b(this.top),D=t.top,O=b(t.bottom)-m,w=x+m,S=this.top+u;else if("left"===a)x=b(this.right),M=this.right-u,k=x-m,P=b(t.left)+m,C=t.right;else if("right"===a)x=b(this.left),P=t.left,C=b(t.right)-m,M=x+m,k=this.left+u;else if("x"===e){if("center"===a)x=b((t.top+t.bottom)/2+.5);else if(o(a)){const t=Object.keys(a)[0],e=a[t];x=b(this.chart.scales[t].getPixelForValue(e))}D=t.top,O=t.bottom,w=x+m,S=w+u}else if("y"===e){if("center"===a)x=b((t.left+t.right)/2);else if(o(a)){const t=Object.keys(a)[0],e=a[t];x=b(this.chart.scales[t].getPixelForValue(e))}M=x-m,k=M-u,P=t.left,C=t.right}const A=l(s.ticks.maxTicksLimit,d),T=Math.max(1,Math.ceil(d/A));for(_=0;_e.value===t));if(i>=0){return e.setContext(this.getContext(i)).lineWidth}return 0}drawGrid(t){const e=this.options.grid,i=this.ctx,s=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(t));let n,o;const a=(t,e,s)=>{s.width&&s.color&&(i.save(),i.lineWidth=s.width,i.strokeStyle=s.color,i.setLineDash(s.borderDash||[]),i.lineDashOffset=s.borderDashOffset,i.beginPath(),i.moveTo(t.x,t.y),i.lineTo(e.x,e.y),i.stroke(),i.restore())};if(e.display)for(n=0,o=s.length;n{this.drawBackground(),this.drawGrid(t),this.drawTitle()}},{z:s,draw:()=>{this.drawBorder()}},{z:e,draw:t=>{this.drawLabels(t)}}]:[{z:e,draw:t=>{this.draw(t)}}]}getMatchingVisibleMetas(t){const e=this.chart.getSortedVisibleDatasetMetas(),i=this.axis+"AxisID",s=[];let n,o;for(n=0,o=e.length;n{const s=i.split("."),n=s.pop(),o=[t].concat(s).join("."),a=e[i].split("."),r=a.pop(),l=a.join(".");ue.route(o,n,l,r)}))}(e,t.defaultRoutes);t.descriptors&&ue.describe(e,t.descriptors)}(t,o,i),this.override&&ue.override(t.id,t.overrides)),o}get(t){return this.items[t]}unregister(t){const e=this.items,i=t.id,s=this.scope;i in e&&delete e[i],s&&i in ue[s]&&(delete ue[s][i],this.override&&delete re[i])}}class Zs{constructor(){this.controllers=new Gs(Vs,"datasets",!0),this.elements=new Gs(Bs,"elements"),this.plugins=new Gs(Object,"plugins"),this.scales=new Gs(Ks,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,e,i){[...e].forEach((e=>{const s=i||this._getRegistryForType(e);i||s.isForType(e)||s===this.plugins&&e.id?this._exec(t,s,e):u(e,(e=>{const s=i||this._getRegistryForType(e);this._exec(t,s,e)}))}))}_exec(t,e,i){const s=w(t);d(i["before"+s],[],i),e[t](i),d(i["after"+s],[],i)}_getRegistryForType(t){for(let e=0;et.filter((t=>!e.some((e=>t.plugin.id===e.plugin.id))));this._notify(s(e,i),t,"stop"),this._notify(s(i,e),t,"start")}}function tn(t,e){return e||!1!==t?!0===t?{}:t:null}function en(t,{plugin:e,local:i},s,n){const o=t.pluginScopeKeys(e),a=t.getOptionScopes(s,o);return i&&e.defaults&&a.push(e.defaults),t.createResolver(a,n,[""],{scriptable:!1,indexable:!1,allKeys:!0})}function sn(t,e){const i=ue.datasets[t]||{};return((e.datasets||{})[t]||{}).indexAxis||e.indexAxis||i.indexAxis||"x"}function nn(t,e){if("x"===t||"y"===t||"r"===t)return t;var i;if(t=e.axis||("top"===(i=e.position)||"bottom"===i?"x":"left"===i||"right"===i?"y":void 0)||t.length>1&&nn(t[0].toLowerCase(),e))return t;throw new Error(`Cannot determine type of '${name}' axis. Please provide 'axis' or 'position' option.`)}function on(t){const e=t.options||(t.options={});e.plugins=l(e.plugins,{}),e.scales=function(t,e){const i=re[t.type]||{scales:{}},s=e.scales||{},n=sn(t.type,e),a=Object.create(null);return Object.keys(s).forEach((t=>{const e=s[t];if(!o(e))return console.error(`Invalid scale configuration for scale: ${t}`);if(e._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${t}`);const r=nn(t,e),l=function(t,e){return t===e?"_index_":"_value_"}(r,n),h=i.scales||{};a[t]=x(Object.create(null),[{axis:r},e,h[r],h[l]])})),t.data.datasets.forEach((i=>{const n=i.type||t.type,o=i.indexAxis||sn(n,e),r=(re[n]||{}).scales||{};Object.keys(r).forEach((t=>{const e=function(t,e){let i=t;return"_index_"===t?i=e:"_value_"===t&&(i="x"===e?"y":"x"),i}(t,o),n=i[e+"AxisID"]||e;a[n]=a[n]||Object.create(null),x(a[n],[{axis:e},s[n],r[t]])}))})),Object.keys(a).forEach((t=>{const e=a[t];x(e,[ue.scales[e.type],ue.scale])})),a}(t,e)}function an(t){return(t=t||{}).datasets=t.datasets||[],t.labels=t.labels||[],t}const rn=new Map,ln=new Set;function hn(t,e){let i=rn.get(t);return i||(i=e(),rn.set(t,i),ln.add(i)),i}const cn=(t,e,i)=>{const s=M(e,i);void 0!==s&&t.add(s)};class dn{constructor(t){this._config=function(t){return(t=t||{}).data=an(t.data),on(t),t}(t),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=an(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){const t=this._config;this.clearCache(),on(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return hn(t,(()=>[[`datasets.${t}`,""]]))}datasetAnimationScopeKeys(t,e){return hn(`${t}.transition.${e}`,(()=>[[`datasets.${t}.transitions.${e}`,`transitions.${e}`],[`datasets.${t}`,""]]))}datasetElementScopeKeys(t,e){return hn(`${t}-${e}`,(()=>[[`datasets.${t}.elements.${e}`,`datasets.${t}`,`elements.${e}`,""]]))}pluginScopeKeys(t){const e=t.id;return hn(`${this.type}-plugin-${e}`,(()=>[[`plugins.${e}`,...t.additionalOptionScopes||[]]]))}_cachedScopes(t,e){const i=this._scopeCache;let s=i.get(t);return s&&!e||(s=new Map,i.set(t,s)),s}getOptionScopes(t,e,i){const{options:s,type:n}=this,o=this._cachedScopes(t,i),a=o.get(e);if(a)return a;const r=new Set;e.forEach((e=>{t&&(r.add(t),e.forEach((e=>cn(r,t,e)))),e.forEach((t=>cn(r,s,t))),e.forEach((t=>cn(r,re[n]||{},t))),e.forEach((t=>cn(r,ue,t))),e.forEach((t=>cn(r,le,t)))}));const l=Array.from(r);return 0===l.length&&l.push(Object.create(null)),ln.has(e)&&o.set(e,l),l}chartOptionScopes(){const{options:t,type:e}=this;return[t,re[e]||{},ue.datasets[e]||{},{type:e},ue,le]}resolveNamedOptions(t,e,i,s=[""]){const o={$shared:!0},{resolver:a,subPrefixes:r}=un(this._resolverCache,t,s);let l=a;if(function(t,e){const{isScriptable:i,isIndexable:s}=$e(t);for(const o of e){const e=i(o),a=s(o),r=(a||e)&&t[o];if(e&&(S(r)||fn(r))||a&&n(r))return!0}return!1}(a,e)){o.$shared=!1;l=je(a,i=S(i)?i():i,this.createResolver(t,i,r))}for(const t of e)o[t]=l[t];return o}createResolver(t,e,i=[""],s){const{resolver:n}=un(this._resolverCache,t,i);return o(e)?je(n,e,void 0,s):n}}function un(t,e,i){let s=t.get(e);s||(s=new Map,t.set(e,s));const n=i.join();let o=s.get(n);if(!o){o={resolver:He(e,i),subPrefixes:i.filter((t=>!t.toLowerCase().includes("hover")))},s.set(n,o)}return o}const fn=t=>o(t)&&Object.getOwnPropertyNames(t).reduce(((e,i)=>e||S(t[i])),!1);const gn=["top","bottom","left","right","chartArea"];function pn(t,e){return"top"===t||"bottom"===t||-1===gn.indexOf(t)&&"x"===e}function mn(t,e){return function(i,s){return i[t]===s[t]?i[e]-s[e]:i[t]-s[t]}}function bn(t){const e=t.chart,i=e.options.animation;e.notifyPlugins("afterRender"),d(i&&i.onComplete,[t],e)}function xn(t){const e=t.chart,i=e.options.animation;d(i&&i.onProgress,[t],e)}function _n(t){return fe()&&"string"==typeof t?t=document.getElementById(t):t&&t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas),t}const yn={},vn=t=>{const e=_n(t);return Object.values(yn).filter((t=>t.canvas===e)).pop()};function Mn(t,e,i){const s=Object.keys(t);for(const n of s){const s=+n;if(s>=e){const o=t[n];delete t[n],(i>0||s>e)&&(t[s+i]=o)}}}class wn{static defaults=ue;static instances=yn;static overrides=re;static registry=Js;static version="4.2.1";static getChart=vn;static register(...t){Js.add(...t),kn()}static unregister(...t){Js.remove(...t),kn()}constructor(t,e){const s=this.config=new dn(e),n=_n(t),o=vn(n);if(o)throw new Error("Canvas is already in use. Chart with ID '"+o.id+"' must be destroyed before the canvas with ID '"+o.canvas.id+"' can be reused.");const a=s.createResolver(s.chartOptionScopes(),this.getContext());this.platform=new(s.platform||vs(n)),this.platform.updateConfig(s);const r=this.platform.acquireContext(n,a.aspectRatio),l=r&&r.canvas,h=l&&l.height,c=l&&l.width;this.id=i(),this.ctx=r,this.canvas=l,this.width=c,this.height=h,this._options=a,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new Qs,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=dt((t=>this.update(t)),a.resizeDelay||0),this._dataChanges=[],yn[this.id]=this,r&&l?(xt.listen(this,"complete",bn),xt.listen(this,"progress",xn),this._initialize(),this.attached&&this.update()):console.error("Failed to create chart: can't acquire context from the given item")}get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:e},width:i,height:n,_aspectRatio:o}=this;return s(t)?e&&o?o:n?i/n:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}get registry(){return Js}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():we(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return Ae(this.canvas,this.ctx),this}stop(){return xt.stop(this),this}resize(t,e){xt.running(this)?this._resizeBeforeDraw={width:t,height:e}:this._resize(t,e)}_resize(t,e){const i=this.options,s=this.canvas,n=i.maintainAspectRatio&&this.aspectRatio,o=this.platform.getMaximumSize(s,t,e,n),a=i.devicePixelRatio||this.platform.getDevicePixelRatio(),r=this.width?"resize":"attach";this.width=o.width,this.height=o.height,this._aspectRatio=this.aspectRatio,we(this,a,!0)&&(this.notifyPlugins("resize",{size:o}),d(i.onResize,[this,o],this),this.attached&&this._doResize(r)&&this.render())}ensureScalesHaveIDs(){u(this.options.scales||{},((t,e)=>{t.id=e}))}buildOrUpdateScales(){const t=this.options,e=t.scales,i=this.scales,s=Object.keys(i).reduce(((t,e)=>(t[e]=!1,t)),{});let n=[];e&&(n=n.concat(Object.keys(e).map((t=>{const i=e[t],s=nn(t,i),n="r"===s,o="x"===s;return{options:i,dposition:n?"chartArea":o?"bottom":"left",dtype:n?"radialLinear":o?"category":"linear"}})))),u(n,(e=>{const n=e.options,o=n.id,a=nn(o,n),r=l(n.type,e.dtype);void 0!==n.position&&pn(n.position,a)===pn(e.dposition)||(n.position=e.dposition),s[o]=!0;let h=null;if(o in i&&i[o].type===r)h=i[o];else{h=new(Js.getScale(r))({id:o,type:r,ctx:this.ctx,chart:this}),i[h.id]=h}h.init(n,t)})),u(s,((t,e)=>{t||delete i[e]})),u(i,(t=>{ns.configure(this,t,t.options),ns.addBox(this,t)}))}_updateMetasets(){const t=this._metasets,e=this.data.datasets.length,i=t.length;if(t.sort(((t,e)=>t.index-e.index)),i>e){for(let t=e;te.length&&delete this._stacks,t.forEach(((t,i)=>{0===e.filter((e=>e===t._dataset)).length&&this._destroyDatasetMeta(i)}))}buildOrUpdateControllers(){const t=[],e=this.data.datasets;let i,s;for(this._removeUnreferencedMetasets(),i=0,s=e.length;i{this.getDatasetMeta(e).controller.reset()}),this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){const e=this.config;e.update();const i=this._options=e.createResolver(e.chartOptionScopes(),this.getContext()),s=this._animationsDisabled=!i.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),!1===this.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0}))return;const n=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let o=0;for(let t=0,e=this.data.datasets.length;t{t.reset()})),this._updateDatasets(t),this.notifyPlugins("afterUpdate",{mode:t}),this._layers.sort(mn("z","_idx"));const{_active:a,_lastEvent:r}=this;r?this._eventHandler(r,!0):a.length&&this._updateHoverStyles(a,a,!0),this.render()}_updateScales(){u(this.scales,(t=>{ns.removeBox(this,t)})),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){const t=this.options,e=new Set(Object.keys(this._listeners)),i=new Set(t.events);P(e,i)&&!!this._responsiveListeners===t.responsive||(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){const{_hiddenIndices:t}=this,e=this._getUniformDataChanges()||[];for(const{method:i,start:s,count:n}of e){Mn(t,s,"_removeElements"===i?-n:n)}}_getUniformDataChanges(){const t=this._dataChanges;if(!t||!t.length)return;this._dataChanges=[];const e=this.data.datasets.length,i=e=>new Set(t.filter((t=>t[0]===e)).map(((t,e)=>e+","+t.splice(1).join(",")))),s=i(0);for(let t=1;tt.split(","))).map((t=>({method:t[1],start:+t[2],count:+t[3]})))}_updateLayout(t){if(!1===this.notifyPlugins("beforeLayout",{cancelable:!0}))return;ns.update(this,this.width,this.height,t);const e=this.chartArea,i=e.width<=0||e.height<=0;this._layers=[],u(this.boxes,(t=>{i&&"chartArea"===t.position||(t.configure&&t.configure(),this._layers.push(...t._layers()))}),this),this._layers.forEach(((t,e)=>{t._idx=e})),this.notifyPlugins("afterLayout")}_updateDatasets(t){if(!1!==this.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})){for(let t=0,e=this.data.datasets.length;t=0;--e)this._drawDataset(t[e]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){const e=this.ctx,i=t._clip,s=!i.disabled,n=function(t){const{xScale:e,yScale:i}=t;if(e&&i)return{left:e.left,right:e.right,top:i.top,bottom:i.bottom}}(t)||this.chartArea,o={meta:t,index:t.index,cancelable:!0};!1!==this.notifyPlugins("beforeDatasetDraw",o)&&(s&&Re(e,{left:!1===i.left?0:n.left-i.left,right:!1===i.right?this.width:n.right+i.right,top:!1===i.top?0:n.top-i.top,bottom:!1===i.bottom?this.height:n.bottom+i.bottom}),t.controller.draw(),s&&Ie(e),o.cancelable=!1,this.notifyPlugins("afterDatasetDraw",o))}isPointInArea(t){return Ee(t,this.chartArea,this._minPadding)}getElementsAtEventForMode(t,e,i,s){const n=Yi.modes[e];return"function"==typeof n?n(this,t,i,s):[]}getDatasetMeta(t){const e=this.data.datasets[t],i=this._metasets;let s=i.filter((t=>t&&t._dataset===e)).pop();return s||(s={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e&&e.order||0,index:t,_dataset:e,_parsed:[],_sorted:!1},i.push(s)),s}getContext(){return this.$context||(this.$context=Pi(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){const e=this.data.datasets[t];if(!e)return!1;const i=this.getDatasetMeta(t);return"boolean"==typeof i.hidden?!i.hidden:!e.hidden}setDatasetVisibility(t,e){this.getDatasetMeta(t).hidden=!e}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateVisibility(t,e,i){const s=i?"show":"hide",n=this.getDatasetMeta(t),o=n.controller._resolveAnimations(void 0,s);k(e)?(n.data[e].hidden=!i,this.update()):(this.setDatasetVisibility(t,i),o.update(n,{visible:i}),this.update((e=>e.datasetIndex===t?s:void 0)))}hide(t,e){this._updateVisibility(t,e,!1)}show(t,e){this._updateVisibility(t,e,!0)}_destroyDatasetMeta(t){const e=this._metasets[t];e&&e.controller&&e.controller._destroy(),delete this._metasets[t]}_stop(){let t,e;for(this.stop(),xt.remove(this),t=0,e=this.data.datasets.length;t{e.addEventListener(this,i,s),t[i]=s},s=(t,e,i)=>{t.offsetX=e,t.offsetY=i,this._eventHandler(t)};u(this.options.events,(t=>i(t,s)))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});const t=this._responsiveListeners,e=this.platform,i=(i,s)=>{e.addEventListener(this,i,s),t[i]=s},s=(i,s)=>{t[i]&&(e.removeEventListener(this,i,s),delete t[i])},n=(t,e)=>{this.canvas&&this.resize(t,e)};let o;const a=()=>{s("attach",a),this.attached=!0,this.resize(),i("resize",n),i("detach",o)};o=()=>{this.attached=!1,s("resize",n),this._stop(),this._resize(0,0),i("attach",a)},e.isAttached(this.canvas)?a():o()}unbindEvents(){u(this._listeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._listeners={},u(this._responsiveListeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._responsiveListeners=void 0}updateHoverStyle(t,e,i){const s=i?"set":"remove";let n,o,a,r;for("dataset"===e&&(n=this.getDatasetMeta(t[0].datasetIndex),n.controller["_"+s+"DatasetHoverStyle"]()),a=0,r=t.length;a{const i=this.getDatasetMeta(t);if(!i)throw new Error("No dataset found at index "+t);return{datasetIndex:t,element:i.data[e],index:e}}));!f(i,e)&&(this._active=i,this._lastEvent=null,this._updateHoverStyles(i,e))}notifyPlugins(t,e,i){return this._plugins.notify(this,t,e,i)}isPluginEnabled(t){return 1===this._plugins._cache.filter((e=>e.plugin.id===t)).length}_updateHoverStyles(t,e,i){const s=this.options.hover,n=(t,e)=>t.filter((t=>!e.some((e=>t.datasetIndex===e.datasetIndex&&t.index===e.index)))),o=n(e,t),a=i?t:n(t,e);o.length&&this.updateHoverStyle(o,s.mode,!1),a.length&&s.mode&&this.updateHoverStyle(a,s.mode,!0)}_eventHandler(t,e){const i={event:t,replay:e,cancelable:!0,inChartArea:this.isPointInArea(t)},s=e=>(e.options.events||this.options.events).includes(t.native.type);if(!1===this.notifyPlugins("beforeEvent",i,s))return;const n=this._handleEvent(t,e,i.inChartArea);return i.cancelable=!1,this.notifyPlugins("afterEvent",i,s),(n||i.changed)&&this.render(),this}_handleEvent(t,e,i){const{_active:s=[],options:n}=this,o=e,a=this._getActiveElements(t,s,i,o),r=D(t),l=function(t,e,i,s){return i&&"mouseout"!==t.type?s?e:t:null}(t,this._lastEvent,i,r);i&&(this._lastEvent=null,d(n.onHover,[t,a,this],this),r&&d(n.onClick,[t,a,this],this));const h=!f(a,s);return(h||e)&&(this._active=a,this._updateHoverStyles(a,s,e)),this._lastEvent=l,h}_getActiveElements(t,e,i,s){if("mouseout"===t.type)return[];if(!i)return e;const n=this.options.hover;return this.getElementsAtEventForMode(t,n.mode,n,s)}}function kn(){return u(wn.instances,(t=>t._plugins.invalidate()))}function Sn(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}class Pn{static override(t){Object.assign(Pn.prototype,t)}constructor(t){this.options=t||{}}init(){}formats(){return Sn()}parse(){return Sn()}format(){return Sn()}add(){return Sn()}diff(){return Sn()}startOf(){return Sn()}endOf(){return Sn()}}var Dn={_date:Pn};function Cn(t){const e=t.iScale,i=function(t,e){if(!t._cache.$bar){const i=t.getMatchingVisibleMetas(e);let s=[];for(let e=0,n=i.length;et-e)))}return t._cache.$bar}(e,t.type);let s,n,o,a,r=e._length;const l=()=>{32767!==o&&-32768!==o&&(k(a)&&(r=Math.min(r,Math.abs(o-a)||r)),a=o)};for(s=0,n=i.length;sMath.abs(r)&&(l=r,h=a),e[i.axis]=h,e._custom={barStart:l,barEnd:h,start:n,end:o,min:a,max:r}}(t,e,i,s):e[i.axis]=i.parse(t,s),e}function An(t,e,i,s){const n=t.iScale,o=t.vScale,a=n.getLabels(),r=n===o,l=[];let h,c,d,u;for(h=i,c=i+s;ht.x,i="left",s="right"):(e=t.base"spacing"!==t,_indexable:t=>"spacing"!==t};static overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const e=t.data;if(e.labels.length&&e.datasets.length){const{labels:{pointStyle:i,color:s}}=t.legend.options;return e.labels.map(((e,n)=>{const o=t.getDatasetMeta(0).controller.getStyle(n);return{text:e,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,fontColor:s,lineWidth:o.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(n),index:n}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}}}};constructor(t,e){super(t,e),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,e){const i=this.getDataset().data,s=this._cachedMeta;if(!1===this._parsing)s._parsed=i;else{let n,a,r=t=>+i[t];if(o(i[t])){const{key:t="value"}=this._parsing;r=e=>+M(i[e],t)}for(n=t,a=t+e;nZ(t,r,l,!0)?1:Math.max(e,e*i,s,s*i),g=(t,e,s)=>Z(t,r,l,!0)?-1:Math.min(e,e*i,s,s*i),p=f(0,h,d),m=f(E,c,u),b=g(C,h,d),x=g(C+E,c,u);s=(p-b)/2,n=(m-x)/2,o=-(p+b)/2,a=-(m+x)/2}return{ratioX:s,ratioY:n,offsetX:o,offsetY:a}}(u,d,r),b=(i.width-o)/f,x=(i.height-o)/g,_=Math.max(Math.min(b,x)/2,0),y=c(this.options.radius,_),v=(y-Math.max(y*r,0))/this._getVisibleDatasetWeightTotal();this.offsetX=p*y,this.offsetY=m*y,s.total=this.calculateTotal(),this.outerRadius=y-v*this._getRingWeightOffset(this.index),this.innerRadius=Math.max(this.outerRadius-v*l,0),this.updateElements(n,0,n.length,t)}_circumference(t,e){const i=this.options,s=this._cachedMeta,n=this._getCircumference();return e&&i.animation.animateRotate||!this.chart.getDataVisibility(t)||null===s._parsed[t]||s.data[t].hidden?0:this.calculateCircumference(s._parsed[t]*n/O)}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.chartArea,r=o.options.animation,l=(a.left+a.right)/2,h=(a.top+a.bottom)/2,c=n&&r.animateScale,d=c?0:this.innerRadius,u=c?0:this.outerRadius,{sharedOptions:f,includeOptions:g}=this._getSharedOptions(e,s);let p,m=this._getRotation();for(p=0;p0&&!isNaN(t)?O*(Math.abs(t)/e):0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=ne(e._parsed[t],i.options.locale);return{label:s[t]||"",value:n}}getMaxBorderWidth(t){let e=0;const i=this.chart;let s,n,o,a,r;if(!t)for(s=0,n=i.data.datasets.length;s{const o=t.getDatasetMeta(0).controller.getStyle(n);return{text:e,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,fontColor:s,lineWidth:o.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(n),index:n}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}}},scales:{r:{type:"radialLinear",angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}};constructor(t,e){super(t,e),this.innerRadius=void 0,this.outerRadius=void 0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=ne(e._parsed[t].r,i.options.locale);return{label:s[t]||"",value:n}}parseObjectData(t,e,i,s){return ei.bind(this)(t,e,i,s)}update(t){const e=this._cachedMeta.data;this._updateRadius(),this.updateElements(e,0,e.length,t)}getMinMax(){const t=this._cachedMeta,e={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY};return t.data.forEach(((t,i)=>{const s=this.getParsed(i).r;!isNaN(s)&&this.chart.getDataVisibility(i)&&(se.max&&(e.max=s))})),e}_updateRadius(){const t=this.chart,e=t.chartArea,i=t.options,s=Math.min(e.right-e.left,e.bottom-e.top),n=Math.max(s/2,0),o=(n-Math.max(i.cutoutPercentage?n/100*i.cutoutPercentage:1,0))/t.getVisibleDatasetCount();this.outerRadius=n-o*this.index,this.innerRadius=this.outerRadius-o}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.options.animation,r=this._cachedMeta.rScale,l=r.xCenter,h=r.yCenter,c=r.getIndexAngle(0)-.5*C;let d,u=c;const f=360/this.countVisibleElements();for(d=0;d{!isNaN(this.getParsed(i).r)&&this.chart.getDataVisibility(i)&&e++})),e}_computeAngle(t,e,i){return this.chart.getDataVisibility(t)?$(this.resolveDataElementOptions(t,e).angle||i):0}}var Vn=Object.freeze({__proto__:null,BarController:class extends Vs{static id="bar";static defaults={datasetElementType:!1,dataElementType:"bar",categoryPercentage:.8,barPercentage:.9,grouped:!0,animations:{numbers:{type:"number",properties:["x","y","base","width","height"]}}};static overrides={scales:{_index_:{type:"category",offset:!0,grid:{offset:!0}},_value_:{type:"linear",beginAtZero:!0}}};parsePrimitiveData(t,e,i,s){return An(t,e,i,s)}parseArrayData(t,e,i,s){return An(t,e,i,s)}parseObjectData(t,e,i,s){const{iScale:n,vScale:o}=t,{xAxisKey:a="x",yAxisKey:r="y"}=this._parsing,l="x"===n.axis?a:r,h="x"===o.axis?a:r,c=[];let d,u,f,g;for(d=i,u=i+s;dt.controller.options.grouped)),o=i.options.stacked,a=[],r=t=>{const i=t.controller.getParsed(e),n=i&&i[t.vScale.axis];if(s(n)||isNaN(n))return!0};for(const i of n)if((void 0===e||!r(i))&&((!1===o||-1===a.indexOf(i.stack)||void 0===o&&void 0===i.stack)&&a.push(i.stack),i.index===t))break;return a.length||a.push(void 0),a}_getStackCount(t){return this._getStacks(void 0,t).length}_getStackIndex(t,e,i){const s=this._getStacks(t,i),n=void 0!==e?s.indexOf(e):-1;return-1===n?s.length-1:n}_getRuler(){const t=this.options,e=this._cachedMeta,i=e.iScale,s=[];let n,o;for(n=0,o=e.data.length;n=i?1:-1)}(u,e,r)*a,f===r&&(b-=u/2);const t=e.getPixelForDecimal(0),s=e.getPixelForDecimal(1),o=Math.min(t,s),h=Math.max(t,s);b=Math.max(Math.min(b,h),o),d=b+u,i&&!c&&(l._stacks[e.axis]._visualValues[n]=e.getValueForPixel(d)-e.getValueForPixel(b))}if(b===e.getPixelForValue(r)){const t=F(u)*e.getLineWidthForValue(r)/2;b+=t,u-=t}return{size:u,base:b,head:d,center:d+u/2}}_calculateBarIndexPixels(t,e){const i=e.scale,n=this.options,o=n.skipNull,a=l(n.maxBarThickness,1/0);let r,h;if(e.grouped){const i=o?this._getStackCount(t):e.stackCount,l="flex"===n.barThickness?function(t,e,i,s){const n=e.pixels,o=n[t];let a=t>0?n[t-1]:null,r=t=0;--i)e=Math.max(e,t[i].size(this.resolveDataElementOptions(i))/2);return e>0&&e}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart.data.labels||[],{xScale:s,yScale:n}=e,o=this.getParsed(t),a=s.getLabelForValue(o.x),r=n.getLabelForValue(o.y),l=o._custom;return{label:i[t]||"",value:"("+a+", "+r+(l?", "+l:"")+")"}}update(t){const e=this._cachedMeta.data;this.updateElements(e,0,e.length,t)}updateElements(t,e,i,s){const n="reset"===s,{iScale:o,vScale:a}=this._cachedMeta,{sharedOptions:r,includeOptions:l}=this._getSharedOptions(e,s),h=o.axis,c=a.axis;for(let d=e;d0&&this.getParsed(e-1);for(let i=0;i<_;++i){const g=t[i],_=b?g:{};if(i=x){_.skip=!0;continue}const v=this.getParsed(i),M=s(v[f]),w=_[u]=a.getPixelForValue(v[u],i),k=_[f]=o||M?r.getBasePixel():r.getPixelForValue(l?this.applyStack(r,v,l):v[f],i);_.skip=isNaN(w)||isNaN(k)||M,_.stop=i>0&&Math.abs(v[u]-y[u])>m,p&&(_.parsed=v,_.raw=h.data[i]),d&&(_.options=c||this.resolveDataElementOptions(i,g.active?"active":n)),b||this.updateElement(g,i,_,n),y=v}}getMaxOverflow(){const t=this._cachedMeta,e=t.dataset,i=e.options&&e.options.borderWidth||0,s=t.data||[];if(!s.length)return i;const n=s[0].size(this.resolveDataElementOptions(0)),o=s[s.length-1].size(this.resolveDataElementOptions(s.length-1));return Math.max(i,n,o)/2}draw(){const t=this._cachedMeta;t.dataset.updateControlPoints(this.chart.chartArea,t.iScale.axis),super.draw()}},PolarAreaController:Fn,PieController:class extends zn{static id="pie";static defaults={cutout:0,rotation:0,circumference:360,radius:"100%"}},RadarController:class extends Vs{static id="radar";static defaults={datasetElementType:"line",dataElementType:"point",indexAxis:"r",showLine:!0,elements:{line:{fill:"start"}}};static overrides={aspectRatio:1,scales:{r:{type:"radialLinear"}}};getLabelAndValue(t){const e=this._cachedMeta.vScale,i=this.getParsed(t);return{label:e.getLabels()[t],value:""+e.getLabelForValue(i[e.axis])}}parseObjectData(t,e,i,s){return ei.bind(this)(t,e,i,s)}update(t){const e=this._cachedMeta,i=e.dataset,s=e.data||[],n=e.iScale.getLabels();if(i.points=s,"resize"!==t){const e=this.resolveDatasetElementOptions(t);this.options.showLine||(e.borderWidth=0);const o={_loop:!0,_fullLoop:n.length===s.length,options:e};this.updateElement(i,void 0,o,t)}this.updateElements(s,0,s.length,t)}updateElements(t,e,i,s){const n=this._cachedMeta.rScale,o="reset"===s;for(let a=e;a0&&this.getParsed(e-1);for(let c=e;c0&&Math.abs(i[f]-_[f])>b,m&&(p.parsed=i,p.raw=h.data[c]),u&&(p.options=d||this.resolveDataElementOptions(c,e.active?"active":n)),x||this.updateElement(e,c,p,n),_=i}this.updateSharedOptions(d,n,c)}getMaxOverflow(){const t=this._cachedMeta,e=t.data||[];if(!this.options.showLine){let t=0;for(let i=e.length-1;i>=0;--i)t=Math.max(t,e[i].size(this.resolveDataElementOptions(i))/2);return t>0&&t}const i=t.dataset,s=i.options&&i.options.borderWidth||0;if(!e.length)return s;const n=e[0].size(this.resolveDataElementOptions(0)),o=e[e.length-1].size(this.resolveDataElementOptions(e.length-1));return Math.max(s,n,o)/2}}});function Bn(t,e,i,s){const n=_i(t.options.borderRadius,["outerStart","outerEnd","innerStart","innerEnd"]);const o=(i-e)/2,a=Math.min(o,s*e/2),r=t=>{const e=(i-Math.min(o,t))*s/2;return J(t,0,Math.min(o,e))};return{outerStart:r(n.outerStart),outerEnd:r(n.outerEnd),innerStart:J(n.innerStart,0,a),innerEnd:J(n.innerEnd,0,a)}}function Nn(t,e,i,s){return{x:i+t*Math.cos(e),y:s+t*Math.sin(e)}}function Wn(t,e,i,s,n,o){const{x:a,y:r,startAngle:l,pixelMargin:h,innerRadius:c}=e,d=Math.max(e.outerRadius+s+i-h,0),u=c>0?c+s+i+h:0;let f=0;const g=n-l;if(s){const t=((c>0?c-s:0)+(d>0?d-s:0))/2;f=(g-(0!==t?g*t/(t+s):g))/2}const p=(g-Math.max(.001,g*d-i/C)/d)/2,m=l+p+f,b=n-p-f,{outerStart:x,outerEnd:_,innerStart:y,innerEnd:v}=Bn(e,u,d,b-m),M=d-x,w=d-_,k=m+x/M,S=b-_/w,P=u+y,D=u+v,O=m+y/P,A=b-v/D;if(t.beginPath(),o){const e=(k+S)/2;if(t.arc(a,r,d,k,e),t.arc(a,r,d,e,S),_>0){const e=Nn(w,S,a,r);t.arc(e.x,e.y,_,S,b+E)}const i=Nn(D,b,a,r);if(t.lineTo(i.x,i.y),v>0){const e=Nn(D,A,a,r);t.arc(e.x,e.y,v,b+E,A+Math.PI)}const s=(b-v/u+(m+y/u))/2;if(t.arc(a,r,u,b-v/u,s,!0),t.arc(a,r,u,s,m+y/u,!0),y>0){const e=Nn(P,O,a,r);t.arc(e.x,e.y,y,O+Math.PI,m-E)}const n=Nn(M,m,a,r);if(t.lineTo(n.x,n.y),x>0){const e=Nn(M,k,a,r);t.arc(e.x,e.y,x,m-E,k)}}else{t.moveTo(a,r);const e=Math.cos(k)*d+a,i=Math.sin(k)*d+r;t.lineTo(e,i);const s=Math.cos(S)*d+a,n=Math.sin(S)*d+r;t.lineTo(s,n)}t.closePath()}function Hn(t,e,i,s,n){const{fullCircles:o,startAngle:a,circumference:r,options:l}=e,{borderWidth:h,borderJoinStyle:c}=l,d="inner"===l.borderAlign;if(!h)return;d?(t.lineWidth=2*h,t.lineJoin=c||"round"):(t.lineWidth=h,t.lineJoin=c||"bevel");let u=e.endAngle;if(o){Wn(t,e,i,s,u,n);for(let e=0;en?(h=n/l,t.arc(o,a,l,i+h,s-h,!0)):t.arc(o,a,n,i+E,s-E),t.closePath(),t.clip()}(t,e,u),o||(Wn(t,e,i,s,u,n),t.stroke())}function jn(t,e,i=e){t.lineCap=l(i.borderCapStyle,e.borderCapStyle),t.setLineDash(l(i.borderDash,e.borderDash)),t.lineDashOffset=l(i.borderDashOffset,e.borderDashOffset),t.lineJoin=l(i.borderJoinStyle,e.borderJoinStyle),t.lineWidth=l(i.borderWidth,e.borderWidth),t.strokeStyle=l(i.borderColor,e.borderColor)}function $n(t,e,i){t.lineTo(i.x,i.y)}function Yn(t,e,i={}){const s=t.length,{start:n=0,end:o=s-1}=i,{start:a,end:r}=e,l=Math.max(n,a),h=Math.min(o,r),c=nr&&o>r;return{count:s,start:l,loop:e.loop,ilen:h(a+(h?r-t:t))%o,_=()=>{f!==g&&(t.lineTo(m,g),t.lineTo(m,f),t.lineTo(m,p))};for(l&&(d=n[x(0)],t.moveTo(d.x,d.y)),c=0;c<=r;++c){if(d=n[x(c)],d.skip)continue;const e=d.x,i=d.y,s=0|e;s===u?(ig&&(g=i),m=(b*m+e)/++b):(_(),t.lineTo(e,i),u=s,b=0,f=g=i),p=i}_()}function qn(t){const e=t.options,i=e.borderDash&&e.borderDash.length;return!(t._decimated||t._loop||e.tension||"monotone"===e.cubicInterpolationMode||e.stepped||i)?Xn:Un}const Kn="function"==typeof Path2D;function Gn(t,e,i,s){Kn&&!e.options.segment?function(t,e,i,s){let n=e._path;n||(n=e._path=new Path2D,e.path(n,i,s)&&n.closePath()),jn(t,e.options),t.stroke(n)}(t,e,i,s):function(t,e,i,s){const{segments:n,options:o}=e,a=qn(e);for(const r of n)jn(t,o,r.style),t.beginPath(),a(t,e,r,{start:i,end:i+s-1})&&t.closePath(),t.stroke()}(t,e,i,s)}class Zn extends Bs{static id="line";static defaults={borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderWidth:3,capBezierPoints:!0,cubicInterpolationMode:"default",fill:!1,spanGaps:!1,stepped:!1,tension:0};static defaultRoutes={backgroundColor:"backgroundColor",borderColor:"borderColor"};static descriptors={_scriptable:!0,_indexable:t=>"borderDash"!==t&&"fill"!==t};constructor(t){super(),this.animated=!0,this.options=void 0,this._chart=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,this._datasetIndex=void 0,t&&Object.assign(this,t)}updateControlPoints(t,e){const i=this.options;if((i.tension||"monotone"===i.cubicInterpolationMode)&&!i.stepped&&!this._pointsUpdated){const s=i.spanGaps?this._loop:this._fullLoop;li(this._points,i,t,s,e),this._pointsUpdated=!0}}set points(t){this._points=t,delete this._segments,delete this._path,this._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=Ri(this,this.options.segment))}first(){const t=this.segments,e=this.points;return t.length&&e[t[0].start]}last(){const t=this.segments,e=this.points,i=t.length;return i&&e[t[i-1].end]}interpolate(t,e){const i=this.options,s=t[e],n=this.points,o=Ei(this,{property:e,start:s,end:s});if(!o.length)return;const a=[],r=function(t){return t.stepped?gi:t.tension||"monotone"===t.cubicInterpolationMode?pi:fi}(i);let l,h;for(l=0,h=o.length;l=O||Z(n,a,r),g=tt(o,h+u,c+u);return f&&g}getCenterPoint(t){const{x:e,y:i,startAngle:s,endAngle:n,innerRadius:o,outerRadius:a}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius"],t),{offset:r,spacing:l}=this.options,h=(s+n)/2,c=(o+a+l+r)/2;return{x:e+Math.cos(h)*c,y:i+Math.sin(h)*c}}tooltipPosition(t){return this.getCenterPoint(t)}draw(t){const{options:e,circumference:i}=this,s=(e.offset||0)/4,n=(e.spacing||0)/2,o=e.circular;if(this.pixelMargin="inner"===e.borderAlign?.33:0,this.fullCircles=i>O?Math.floor(i/O):0,0===i||this.innerRadius<0||this.outerRadius<0)return;t.save();const a=(this.startAngle+this.endAngle)/2;t.translate(Math.cos(a)*s,Math.sin(a)*s);const r=s*(1-Math.sin(Math.min(C,i||0)));t.fillStyle=e.backgroundColor,t.strokeStyle=e.borderColor,function(t,e,i,s,n){const{fullCircles:o,startAngle:a,circumference:r}=e;let l=e.endAngle;if(o){Wn(t,e,i,s,l,n);for(let e=0;e("string"==typeof e?(i=t.push(e)-1,s.unshift({index:i,label:e})):isNaN(e)&&(i=null),i))(t,e,i,s);return n!==t.lastIndexOf(e)?i:n}function ro(t){const e=this.getLabels();return t>=0&&ts=e?s:t,a=t=>n=i?n:t;if(t){const t=F(s),e=F(n);t<0&&e<0?a(0):t>0&&e>0&&o(0)}if(s===n){let e=0===n?1:Math.abs(.05*n);a(n+e),t||o(s-e)}this.min=s,this.max=n}getTickLimit(){const t=this.options.ticks;let e,{maxTicksLimit:i,stepSize:s}=t;return s?(e=Math.ceil(this.max/s)-Math.floor(this.min/s)+1,e>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${s} would result generating up to ${e} ticks. Limiting to 1000.`),e=1e3)):(e=this.computeTickLimit(),i=i||11),i&&(e=Math.min(i,e)),e}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const t=this.options,e=t.ticks;let i=this.getTickLimit();i=Math.max(2,i);const n=function(t,e){const i=[],{bounds:n,step:o,min:a,max:r,precision:l,count:h,maxTicks:c,maxDigits:d,includeBounds:u}=t,f=o||1,g=c-1,{min:p,max:m}=e,b=!s(a),x=!s(r),_=!s(h),y=(m-p)/(d+1);let v,M,w,k,S=B((m-p)/g/f)*f;if(S<1e-14&&!b&&!x)return[{value:p},{value:m}];k=Math.ceil(m/S)-Math.floor(p/S),k>g&&(S=B(k*S/g/f)*f),s(l)||(v=Math.pow(10,l),S=Math.ceil(S*v)/v),"ticks"===n?(M=Math.floor(p/S)*S,w=Math.ceil(m/S)*S):(M=p,w=m),b&&x&&o&&H((r-a)/o,S/1e3)?(k=Math.round(Math.min((r-a)/S,c)),S=(r-a)/k,M=a,w=r):_?(M=b?a:M,w=x?r:w,k=h-1,S=(w-M)/k):(k=(w-M)/S,k=V(k,Math.round(k),S/1e3)?Math.round(k):Math.ceil(k));const P=Math.max(U(S),U(M));v=Math.pow(10,s(l)?P:l),M=Math.round(M*v)/v,w=Math.round(w*v)/v;let D=0;for(b&&(u&&M!==a?(i.push({value:a}),MMath.floor(z(t)),fo=(t,e)=>Math.pow(10,uo(t)+e);function go(t){return 1===t/Math.pow(10,uo(t))}function po(t,e,i){const s=Math.pow(10,i),n=Math.floor(t/s);return Math.ceil(e/s)-n}function mo(t,{min:e,max:i}){e=r(t.min,e);const s=[],n=uo(e);let o=function(t,e){let i=uo(e-t);for(;po(t,e,i)>10;)i++;for(;po(t,e,i)<10;)i--;return Math.min(i,uo(t))}(e,i),a=o<0?Math.pow(10,Math.abs(o)):1;const l=Math.pow(10,o),h=n>o?Math.pow(10,n):0,c=Math.round((e-h)*a)/a,d=Math.floor((e-h)/l/10)*l*10;let u=Math.floor((c-d)/Math.pow(10,o)),f=r(t.min,Math.round((h+d+u*Math.pow(10,o))*a)/a);for(;f=10?u=u<15?15:20:u++,u>=20&&(o++,u=2,a=o>=0?1:a),f=Math.round((h+d+u*Math.pow(10,o))*a)/a;const g=r(t.max,f);return s.push({value:g,major:go(g),significand:u}),s}class bo extends Ks{static id="logarithmic";static defaults={ticks:{callback:ae.formatters.logarithmic,major:{enabled:!0}}};constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._valueRange=0}parse(t,e){const i=ho.prototype.parse.apply(this,[t,e]);if(0!==i)return a(i)&&i>0?i:null;this._zero=!0}determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=a(t)?Math.max(0,t):null,this.max=a(e)?Math.max(0,e):null,this.options.beginAtZero&&(this._zero=!0),this._zero&&this.min!==this._suggestedMin&&!a(this._userMin)&&(this.min=t===fo(this.min,0)?fo(this.min,-1):fo(this.min,0)),this.handleTickRangeOptions()}handleTickRangeOptions(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let i=this.min,s=this.max;const n=e=>i=t?i:e,o=t=>s=e?s:t;i===s&&(i<=0?(n(1),o(10)):(n(fo(i,-1)),o(fo(s,1)))),i<=0&&n(fo(s,-1)),s<=0&&o(fo(i,1)),this.min=i,this.max=s}buildTicks(){const t=this.options,e=mo({min:this._userMin,max:this._userMax},this);return"ticks"===t.bounds&&j(e,this,"value"),t.reverse?(e.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),e}getLabelForValue(t){return void 0===t?"0":ne(t,this.chart.options.locale,this.options.ticks.format)}configure(){const t=this.min;super.configure(),this._startValue=z(t),this._valueRange=z(this.max)-z(t)}getPixelForValue(t){return void 0!==t&&0!==t||(t=this.min),null===t||isNaN(t)?NaN:this.getPixelForDecimal(t===this.min?0:(z(t)-this._startValue)/this._valueRange)}getValueForPixel(t){const e=this.getDecimalForPixel(t);return Math.pow(10,this._startValue+e*this._valueRange)}}function xo(t){const e=t.ticks;if(e.display&&t.display){const t=Mi(e.backdropPadding);return l(e.font&&e.font.size,ue.font.size)+t.height}return 0}function _o(t,e,i,s,n){return t===s||t===n?{start:e-i/2,end:e+i/2}:tn?{start:e-i,end:e}:{start:e,end:e+i}}function yo(t){const e={l:t.left+t._padding.left,r:t.right-t._padding.right,t:t.top+t._padding.top,b:t.bottom-t._padding.bottom},i=Object.assign({},e),s=[],o=[],a=t._pointLabels.length,r=t.options.pointLabels,l=r.centerPointLabels?C/a:0;for(let u=0;ue.r&&(r=(s.end-e.r)/o,t.r=Math.max(t.r,e.r+r)),n.starte.b&&(l=(n.end-e.b)/a,t.b=Math.max(t.b,e.b+l))}function Mo(t){return 0===t||180===t?"center":t<180?"left":"right"}function wo(t,e,i){return"right"===i?t-=e:"center"===i&&(t-=e/2),t}function ko(t,e,i){return 90===i||270===i?t-=e/2:(i>270||i<90)&&(t-=e),t}function So(t,e,i,s){const{ctx:n}=t;if(i)n.arc(t.xCenter,t.yCenter,e,0,O);else{let i=t.getPointPosition(0,e);n.moveTo(i.x,i.y);for(let o=1;ot,padding:5,centerPointLabels:!1}};static defaultRoutes={"angleLines.color":"borderColor","pointLabels.color":"color","ticks.color":"color"};static descriptors={angleLines:{_fallback:"grid"}};constructor(t){super(t),this.xCenter=void 0,this.yCenter=void 0,this.drawingArea=void 0,this._pointLabels=[],this._pointLabelItems=[]}setDimensions(){const t=this._padding=Mi(xo(this.options)/2),e=this.width=this.maxWidth-t.width,i=this.height=this.maxHeight-t.height;this.xCenter=Math.floor(this.left+e/2+t.left),this.yCenter=Math.floor(this.top+i/2+t.top),this.drawingArea=Math.floor(Math.min(e,i)/2)}determineDataLimits(){const{min:t,max:e}=this.getMinMax(!1);this.min=a(t)&&!isNaN(t)?t:0,this.max=a(e)&&!isNaN(e)?e:0,this.handleTickRangeOptions()}computeTickLimit(){return Math.ceil(this.drawingArea/xo(this.options))}generateTickLabels(t){ho.prototype.generateTickLabels.call(this,t),this._pointLabels=this.getLabels().map(((t,e)=>{const i=d(this.options.pointLabels.callback,[t,e],this);return i||0===i?i:""})).filter(((t,e)=>this.chart.getDataVisibility(e)))}fit(){const t=this.options;t.display&&t.pointLabels.display?yo(this):this.setCenterPoint(0,0,0,0)}setCenterPoint(t,e,i,s){this.xCenter+=Math.floor((t-e)/2),this.yCenter+=Math.floor((i-s)/2),this.drawingArea-=Math.min(this.drawingArea/2,Math.max(t,e,i,s))}getIndexAngle(t){return G(t*(O/(this._pointLabels.length||1))+$(this.options.startAngle||0))}getDistanceFromCenterForValue(t){if(s(t))return NaN;const e=this.drawingArea/(this.max-this.min);return this.options.reverse?(this.max-t)*e:(t-this.min)*e}getValueForDistanceFromCenter(t){if(s(t))return NaN;const e=t/(this.drawingArea/(this.max-this.min));return this.options.reverse?this.max-e:this.min+e}getPointLabelContext(t){const e=this._pointLabels||[];if(t>=0&&t=0;o--){const e=n.setContext(t.getPointLabelContext(o)),a=wi(e.font),{x:r,y:l,textAlign:h,left:c,top:d,right:u,bottom:f}=t._pointLabelItems[o],{backdropColor:g}=e;if(!s(g)){const t=vi(e.borderRadius),s=Mi(e.backdropPadding);i.fillStyle=g;const n=c-s.left,o=d-s.top,a=u-c+s.width,r=f-d+s.height;Object.values(t).some((t=>0!==t))?(i.beginPath(),We(i,{x:n,y:o,w:a,h:r,radius:t}),i.fill()):i.fillRect(n,o,a,r)}Ve(i,t._pointLabels[o],r,l+a.lineHeight/2,a,{color:e.color,textAlign:h,textBaseline:"middle"})}}(this,a),n.display&&this.ticks.forEach(((t,e)=>{if(0!==e){l=this.getDistanceFromCenterForValue(t.value);const i=this.getContext(e),s=n.setContext(i),r=o.setContext(i);!function(t,e,i,s,n){const o=t.ctx,a=e.circular,{color:r,lineWidth:l}=e;!a&&!s||!r||!l||i<0||(o.save(),o.strokeStyle=r,o.lineWidth=l,o.setLineDash(n.dash),o.lineDashOffset=n.dashOffset,o.beginPath(),So(t,i,a,s),o.closePath(),o.stroke(),o.restore())}(this,s,l,a,r)}})),i.display){for(t.save(),r=a-1;r>=0;r--){const s=i.setContext(this.getPointLabelContext(r)),{color:n,lineWidth:o}=s;o&&n&&(t.lineWidth=o,t.strokeStyle=n,t.setLineDash(s.borderDash),t.lineDashOffset=s.borderDashOffset,l=this.getDistanceFromCenterForValue(e.ticks.reverse?this.min:this.max),h=this.getPointPosition(r,l),t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(h.x,h.y),t.stroke())}t.restore()}}drawBorder(){}drawLabels(){const t=this.ctx,e=this.options,i=e.ticks;if(!i.display)return;const s=this.getIndexAngle(0);let n,o;t.save(),t.translate(this.xCenter,this.yCenter),t.rotate(s),t.textAlign="center",t.textBaseline="middle",this.ticks.forEach(((s,a)=>{if(0===a&&!e.reverse)return;const r=i.setContext(this.getContext(a)),l=wi(r.font);if(n=this.getDistanceFromCenterForValue(this.ticks[a].value),r.showLabelBackdrop){t.font=l.string,o=t.measureText(s.label).width,t.fillStyle=r.backdropColor;const e=Mi(r.backdropPadding);t.fillRect(-o/2-e.left,-n-l.size/2-e.top,o+e.width,l.size+e.height)}Ve(t,s.label,0,-n,l,{color:r.color})})),t.restore()}drawTitle(){}}const Do={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},Co=Object.keys(Do);function Oo(t,e){return t-e}function Ao(t,e){if(s(e))return null;const i=t._adapter,{parser:n,round:o,isoWeekday:r}=t._parseOpts;let l=e;return"function"==typeof n&&(l=n(l)),a(l)||(l="string"==typeof n?i.parse(l,n):i.parse(l)),null===l?null:(o&&(l="week"!==o||!W(r)&&!0!==r?i.startOf(l,o):i.startOf(l,"isoWeek",r)),+l)}function To(t,e,i,s){const n=Co.length;for(let o=Co.indexOf(t);o=e?i[s]:i[n]]=!0}}else t[e]=!0}function Eo(t,e,i){const s=[],n={},o=e.length;let a,r;for(a=0;a=0&&(e[l].major=!0);return e}(t,s,n,i):s}class Ro extends Ks{static id="time";static defaults={bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{source:"auto",callback:!1,major:{enabled:!1}}};constructor(t){super(t),this._cache={data:[],labels:[],all:[]},this._unit="day",this._majorUnit=void 0,this._offsets={},this._normalized=!1,this._parseOpts=void 0}init(t,e={}){const i=t.time||(t.time={}),s=this._adapter=new Dn._date(t.adapters.date);s.init(e),x(i.displayFormats,s.formats()),this._parseOpts={parser:i.parser,round:i.round,isoWeekday:i.isoWeekday},super.init(t),this._normalized=e.normalized}parse(t,e){return void 0===t?null:Ao(this,t)}beforeLayout(){super.beforeLayout(),this._cache={data:[],labels:[],all:[]}}determineDataLimits(){const t=this.options,e=this._adapter,i=t.time.unit||"day";let{min:s,max:n,minDefined:o,maxDefined:r}=this.getUserBounds();function l(t){o||isNaN(t.min)||(s=Math.min(s,t.min)),r||isNaN(t.max)||(n=Math.max(n,t.max))}o&&r||(l(this._getLabelBounds()),"ticks"===t.bounds&&"labels"===t.ticks.source||l(this.getMinMax(!1))),s=a(s)&&!isNaN(s)?s:+e.startOf(Date.now(),i),n=a(n)&&!isNaN(n)?n:+e.endOf(Date.now(),i)+1,this.min=Math.min(s,n-1),this.max=Math.max(s+1,n)}_getLabelBounds(){const t=this.getLabelTimestamps();let e=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;return t.length&&(e=t[0],i=t[t.length-1]),{min:e,max:i}}buildTicks(){const t=this.options,e=t.time,i=t.ticks,s="labels"===i.source?this.getLabelTimestamps():this._generate();"ticks"===t.bounds&&s.length&&(this.min=this._userMin||s[0],this.max=this._userMax||s[s.length-1]);const n=this.min,o=nt(s,n,this.max);return this._unit=e.unit||(i.autoSkip?To(e.minUnit,this.min,this.max,this._getLabelCapacity(n)):function(t,e,i,s,n){for(let o=Co.length-1;o>=Co.indexOf(i);o--){const i=Co[o];if(Do[i].common&&t._adapter.diff(n,s,i)>=e-1)return i}return Co[i?Co.indexOf(i):0]}(this,o.length,e.minUnit,this.min,this.max)),this._majorUnit=i.major.enabled&&"year"!==this._unit?function(t){for(let e=Co.indexOf(t)+1,i=Co.length;e+t.value)))}initOffsets(t=[]){let e,i,s=0,n=0;this.options.offset&&t.length&&(e=this.getDecimalForValue(t[0]),s=1===t.length?1-e:(this.getDecimalForValue(t[1])-e)/2,i=this.getDecimalForValue(t[t.length-1]),n=1===t.length?i:(i-this.getDecimalForValue(t[t.length-2]))/2);const o=t.length<3?.5:.25;s=J(s,0,o),n=J(n,0,o),this._offsets={start:s,end:n,factor:1/(s+1+n)}}_generate(){const t=this._adapter,e=this.min,i=this.max,s=this.options,n=s.time,o=n.unit||To(n.minUnit,e,i,this._getLabelCapacity(e)),a=l(s.ticks.stepSize,1),r="week"===o&&n.isoWeekday,h=W(r)||!0===r,c={};let d,u,f=e;if(h&&(f=+t.startOf(f,"isoWeek",r)),f=+t.startOf(f,h?"day":o),t.diff(i,e,o)>1e5*a)throw new Error(e+" and "+i+" are too far apart with stepSize of "+a+" "+o);const g="data"===s.ticks.source&&this.getDataTimestamps();for(d=f,u=0;dt-e)).map((t=>+t))}getLabelForValue(t){const e=this._adapter,i=this.options.time;return i.tooltipFormat?e.format(t,i.tooltipFormat):e.format(t,i.displayFormats.datetime)}format(t,e){const i=this.options.time.displayFormats,s=this._unit,n=e||i[s];return this._adapter.format(t,n)}_tickFormatFunction(t,e,i,s){const n=this.options,o=n.ticks.callback;if(o)return d(o,[t,e,i],this);const a=n.time.displayFormats,r=this._unit,l=this._majorUnit,h=r&&a[r],c=l&&a[l],u=i[e],f=l&&c&&u&&u.major;return this._adapter.format(t,s||(f?c:h))}generateTickLabels(t){let e,i,s;for(e=0,i=t.length;e0?a:1}getDataTimestamps(){let t,e,i=this._cache.data||[];if(i.length)return i;const s=this.getMatchingVisibleMetas();if(this._normalized&&s.length)return this._cache.data=s[0].controller.getAllParsedValues(this);for(t=0,e=s.length;t=t[r].pos&&e<=t[l].pos&&({lo:r,hi:l}=it(t,"pos",e)),({pos:s,time:o}=t[r]),({pos:n,time:a}=t[l])):(e>=t[r].time&&e<=t[l].time&&({lo:r,hi:l}=it(t,"time",e)),({time:s,pos:o}=t[r]),({time:n,pos:a}=t[l]));const h=n-s;return h?o+(a-o)*(e-s)/h:o}var zo=Object.freeze({__proto__:null,CategoryScale:class extends Ks{static id="category";static defaults={ticks:{callback:ro}};constructor(t){super(t),this._startValue=void 0,this._valueRange=0,this._addedLabels=[]}init(t){const e=this._addedLabels;if(e.length){const t=this.getLabels();for(const{index:i,label:s}of e)t[i]===s&&t.splice(i,1);this._addedLabels=[]}super.init(t)}parse(t,e){if(s(t))return null;const i=this.getLabels();return((t,e)=>null===t?null:J(Math.round(t),0,e))(e=isFinite(e)&&i[e]===t?e:ao(i,t,l(e,t),this._addedLabels),i.length-1)}determineDataLimits(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let{min:i,max:s}=this.getMinMax(!0);"ticks"===this.options.bounds&&(t||(i=0),e||(s=this.getLabels().length-1)),this.min=i,this.max=s}buildTicks(){const t=this.min,e=this.max,i=this.options.offset,s=[];let n=this.getLabels();n=0===t&&e===n.length-1?n:n.slice(t,e+1),this._valueRange=Math.max(n.length-(i?0:1),1),this._startValue=this.min-(i?.5:0);for(let i=t;i<=e;i++)s.push({value:i});return s}getLabelForValue(t){return ro.call(this,t)}configure(){super.configure(),this.isHorizontal()||(this._reversePixels=!this._reversePixels)}getPixelForValue(t){return"number"!=typeof t&&(t=this.parse(t)),null===t?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getValueForPixel(t){return Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange)}getBasePixel(){return this.bottom}},LinearScale:co,LogarithmicScale:bo,RadialLinearScale:Po,TimeScale:Ro,TimeSeriesScale:class extends Ro{static id="timeseries";static defaults=Ro.defaults;constructor(t){super(t),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){const t=this._getTimestampsForTable(),e=this._table=this.buildLookupTable(t);this._minPos=Io(e,this.min),this._tableRange=Io(e,this.max)-this._minPos,super.initOffsets(t)}buildLookupTable(t){const{min:e,max:i}=this,s=[],n=[];let o,a,r,l,h;for(o=0,a=t.length;o=e&&l<=i&&s.push(l);if(s.length<2)return[{time:e,pos:0},{time:i,pos:1}];for(o=0,a=s.length;ot.replace("rgb(","rgba(").replace(")",", 0.5)")));function Bo(t){return Fo[t%Fo.length]}function No(t){return Vo[t%Vo.length]}function Wo(t){let e=0;return(i,s)=>{const n=t.getDatasetMeta(s).controller;n instanceof zn?e=function(t,e){return t.backgroundColor=t.data.map((()=>Bo(e++))),e}(i,e):n instanceof Fn?e=function(t,e){return t.backgroundColor=t.data.map((()=>No(e++))),e}(i,e):n&&(e=function(t,e){return t.borderColor=Bo(e),t.backgroundColor=No(e),++e}(i,e))}}function Ho(t){let e;for(e in t)if(t[e].borderColor||t[e].backgroundColor)return!0;return!1}var jo={id:"colors",defaults:{enabled:!0,forceOverride:!1},beforeLayout(t,e,i){if(!i.enabled)return;const{data:{datasets:s},options:n}=t.config,{elements:o}=n;if(!i.forceOverride&&(Ho(s)||(a=n)&&(a.borderColor||a.backgroundColor)||o&&Ho(o)))return;var a;const r=Wo(t);s.forEach(r)}};function $o(t){if(t._decimated){const e=t._data;delete t._decimated,delete t._data,Object.defineProperty(t,"data",{configurable:!0,enumerable:!0,writable:!0,value:e})}}function Yo(t){t.data.datasets.forEach((t=>{$o(t)}))}var Uo={id:"decimation",defaults:{algorithm:"min-max",enabled:!1},beforeElementsUpdate:(t,e,i)=>{if(!i.enabled)return void Yo(t);const n=t.width;t.data.datasets.forEach(((e,o)=>{const{_data:a,indexAxis:r}=e,l=t.getDatasetMeta(o),h=a||e.data;if("y"===ki([r,t.options.indexAxis]))return;if(!l.controller.supportsDecimation)return;const c=t.scales[l.xAxisID];if("linear"!==c.type&&"time"!==c.type)return;if(t.options.parsing)return;let{start:d,count:u}=function(t,e){const i=e.length;let s,n=0;const{iScale:o}=t,{min:a,max:r,minDefined:l,maxDefined:h}=o.getUserBounds();return l&&(n=J(it(e,o.axis,a).lo,0,i-1)),s=h?J(it(e,o.axis,r).hi+1,n,i)-n:i-n,{start:n,count:s}}(l,h);if(u<=(i.threshold||4*n))return void $o(e);let f;switch(s(a)&&(e._data=h,delete e.data,Object.defineProperty(e,"data",{configurable:!0,enumerable:!0,get:function(){return this._decimated},set:function(t){this._data=t}})),i.algorithm){case"lttb":f=function(t,e,i,s,n){const o=n.samples||s;if(o>=i)return t.slice(e,e+i);const a=[],r=(i-2)/(o-2);let l=0;const h=e+i-1;let c,d,u,f,g,p=e;for(a[l++]=t[p],c=0;cu&&(u=f,d=t[s],g=s);a[l++]=d,p=g}return a[l++]=t[h],a}(h,d,u,n,i);break;case"min-max":f=function(t,e,i,n){let o,a,r,l,h,c,d,u,f,g,p=0,m=0;const b=[],x=e+i-1,_=t[e].x,y=t[x].x-_;for(o=e;og&&(g=l,d=o),p=(m*p+a.x)/++m;else{const i=o-1;if(!s(c)&&!s(d)){const e=Math.min(c,d),s=Math.max(c,d);e!==u&&e!==i&&b.push({...t[e],x:p}),s!==u&&s!==i&&b.push({...t[s],x:p})}o>0&&i!==u&&b.push(t[i]),b.push(a),h=e,m=0,f=g=l,c=d=u=o}}return b}(h,d,u,n);break;default:throw new Error(`Unsupported decimation algorithm '${i.algorithm}'`)}e._decimated=f}))},destroy(t){Yo(t)}};function Xo(t,e,i,s){if(s)return;let n=e[t],o=i[t];return"angle"===t&&(n=G(n),o=G(o)),{property:t,start:n,end:o}}function qo(t,e,i){for(;e>t;e--){const t=i[e];if(!isNaN(t.x)&&!isNaN(t.y))break}return e}function Ko(t,e,i,s){return t&&e?s(t[i],e[i]):t?t[i]:e?e[i]:0}function Go(t,e){let i=[],s=!1;return n(t)?(s=!0,i=t):i=function(t,e){const{x:i=null,y:s=null}=t||{},n=e.points,o=[];return e.segments.forEach((({start:t,end:e})=>{e=qo(t,e,n);const a=n[t],r=n[e];null!==s?(o.push({x:a.x,y:s}),o.push({x:r.x,y:s})):null!==i&&(o.push({x:i,y:a.y}),o.push({x:i,y:r.y}))})),o}(t,e),i.length?new Zn({points:i,options:{tension:0},_loop:s,_fullLoop:s}):null}function Zo(t){return t&&!1!==t.fill}function Jo(t,e,i){let s=t[e].fill;const n=[e];let o;if(!i)return s;for(;!1!==s&&-1===n.indexOf(s);){if(!a(s))return s;if(o=t[s],!o)return!1;if(o.visible)return s;n.push(s),s=o.fill}return!1}function Qo(t,e,i){const s=function(t){const e=t.options,i=e.fill;let s=l(i&&i.target,i);void 0===s&&(s=!!e.backgroundColor);if(!1===s||null===s)return!1;if(!0===s)return"origin";return s}(t);if(o(s))return!isNaN(s.value)&&s;let n=parseFloat(s);return a(n)&&Math.floor(n)===n?function(t,e,i,s){"-"!==t&&"+"!==t||(i=e+i);if(i===e||i<0||i>=s)return!1;return i}(s[0],e,n,i):["origin","start","end","stack","shape"].indexOf(s)>=0&&s}function ta(t,e,i){const s=[];for(let n=0;n=0;--e){const i=n[e].$filler;i&&(i.line.updateControlPoints(o,i.axis),s&&i.fill&&na(t.ctx,i,o))}},beforeDatasetsDraw(t,e,i){if("beforeDatasetsDraw"!==i.drawTime)return;const s=t.getSortedVisibleDatasetMetas();for(let e=s.length-1;e>=0;--e){const i=s[e].$filler;Zo(i)&&na(t.ctx,i,t.chartArea)}},beforeDatasetDraw(t,e,i){const s=e.meta.$filler;Zo(s)&&"beforeDatasetDraw"===i.drawTime&&na(t.ctx,s,t.chartArea)},defaults:{propagate:!0,drawTime:"beforeDatasetDraw"}};const ca=(t,e)=>{let{boxHeight:i=e,boxWidth:s=e}=t;return t.usePointStyle&&(i=Math.min(i,e),s=t.pointStyleWidth||Math.min(s,e)),{boxWidth:s,boxHeight:i,itemHeight:Math.max(e,i)}};class da extends Bs{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e,i){this.maxWidth=t,this.maxHeight=e,this._margins=i,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){const t=this.options.labels||{};let e=d(t.generateLabels,[this.chart],this)||[];t.filter&&(e=e.filter((e=>t.filter(e,this.chart.data)))),t.sort&&(e=e.sort(((e,i)=>t.sort(e,i,this.chart.data)))),this.options.reverse&&e.reverse(),this.legendItems=e}fit(){const{options:t,ctx:e}=this;if(!t.display)return void(this.width=this.height=0);const i=t.labels,s=wi(i.font),n=s.size,o=this._computeTitleHeight(),{boxWidth:a,itemHeight:r}=ca(i,n);let l,h;e.font=s.string,this.isHorizontal()?(l=this.maxWidth,h=this._fitRows(o,n,a,r)+10):(h=this.maxHeight,l=this._fitCols(o,s,a,r)+10),this.width=Math.min(l,t.maxWidth||this.maxWidth),this.height=Math.min(h,t.maxHeight||this.maxHeight)}_fitRows(t,e,i,s){const{ctx:n,maxWidth:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.lineWidths=[0],h=s+a;let c=t;n.textAlign="left",n.textBaseline="middle";let d=-1,u=-h;return this.legendItems.forEach(((t,f)=>{const g=i+e/2+n.measureText(t.text).width;(0===f||l[l.length-1]+g+2*a>o)&&(c+=h,l[l.length-(f>0?0:1)]=0,u+=h,d++),r[f]={left:0,top:u,row:d,width:g,height:s},l[l.length-1]+=g+a})),c}_fitCols(t,e,i,s){const{ctx:n,maxHeight:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.columnSizes=[],h=o-t;let c=a,d=0,u=0,f=0,g=0;return this.legendItems.forEach(((t,o)=>{const{itemWidth:p,itemHeight:m}=function(t,e,i,s,n){const o=function(t,e,i,s){let n=t.text;n&&"string"!=typeof n&&(n=n.reduce(((t,e)=>t.length>e.length?t:e)));return e+i.size/2+s.measureText(n).width}(s,t,e,i),a=function(t,e,i){let s=t;"string"!=typeof e.text&&(s=ua(e,i));return s}(n,s,e.lineHeight);return{itemWidth:o,itemHeight:a}}(i,e,n,t,s);o>0&&u+m+2*a>h&&(c+=d+a,l.push({width:d,height:u}),f+=d+a,g++,d=u=0),r[o]={left:f,top:u,col:g,width:p,height:m},d=Math.max(d,p),u+=m+a})),c+=d,l.push({width:d,height:u}),c}adjustHitBoxes(){if(!this.options.display)return;const t=this._computeTitleHeight(),{legendHitBoxes:e,options:{align:i,labels:{padding:s},rtl:n}}=this,o=Di(n,this.left,this.width);if(this.isHorizontal()){let n=0,a=ft(i,this.left+s,this.right-this.lineWidths[n]);for(const r of e)n!==r.row&&(n=r.row,a=ft(i,this.left+s,this.right-this.lineWidths[n])),r.top+=this.top+t+s,r.left=o.leftForLtr(o.x(a),r.width),a+=r.width+s}else{let n=0,a=ft(i,this.top+t+s,this.bottom-this.columnSizes[n].height);for(const r of e)r.col!==n&&(n=r.col,a=ft(i,this.top+t+s,this.bottom-this.columnSizes[n].height)),r.top=a,r.left+=this.left+s,r.left=o.leftForLtr(o.x(r.left),r.width),a+=r.height+s}}isHorizontal(){return"top"===this.options.position||"bottom"===this.options.position}draw(){if(this.options.display){const t=this.ctx;Re(t,this),this._draw(),Ie(t)}}_draw(){const{options:t,columnSizes:e,lineWidths:i,ctx:s}=this,{align:n,labels:o}=t,a=ue.color,r=Di(t.rtl,this.left,this.width),h=wi(o.font),{padding:c}=o,d=h.size,u=d/2;let f;this.drawTitle(),s.textAlign=r.textAlign("left"),s.textBaseline="middle",s.lineWidth=.5,s.font=h.string;const{boxWidth:g,boxHeight:p,itemHeight:m}=ca(o,d),b=this.isHorizontal(),x=this._computeTitleHeight();f=b?{x:ft(n,this.left+c,this.right-i[0]),y:this.top+c+x,line:0}:{x:this.left+c,y:ft(n,this.top+x+c,this.bottom-e[0].height),line:0},Ci(this.ctx,t.textDirection);const _=m+c;this.legendItems.forEach(((y,v)=>{s.strokeStyle=y.fontColor,s.fillStyle=y.fontColor;const M=s.measureText(y.text).width,w=r.textAlign(y.textAlign||(y.textAlign=o.textAlign)),k=g+u+M;let S=f.x,P=f.y;r.setWidth(this.width),b?v>0&&S+k+c>this.right&&(P=f.y+=_,f.line++,S=f.x=ft(n,this.left+c,this.right-i[f.line])):v>0&&P+_>this.bottom&&(S=f.x=S+e[f.line].width+c,f.line++,P=f.y=ft(n,this.top+x+c,this.bottom-e[f.line].height));if(function(t,e,i){if(isNaN(g)||g<=0||isNaN(p)||p<0)return;s.save();const n=l(i.lineWidth,1);if(s.fillStyle=l(i.fillStyle,a),s.lineCap=l(i.lineCap,"butt"),s.lineDashOffset=l(i.lineDashOffset,0),s.lineJoin=l(i.lineJoin,"miter"),s.lineWidth=n,s.strokeStyle=l(i.strokeStyle,a),s.setLineDash(l(i.lineDash,[])),o.usePointStyle){const a={radius:p*Math.SQRT2/2,pointStyle:i.pointStyle,rotation:i.rotation,borderWidth:n},l=r.xPlus(t,g/2);Le(s,a,l,e+u,o.pointStyleWidth&&g)}else{const o=e+Math.max((d-p)/2,0),a=r.leftForLtr(t,g),l=vi(i.borderRadius);s.beginPath(),Object.values(l).some((t=>0!==t))?We(s,{x:a,y:o,w:g,h:p,radius:l}):s.rect(a,o,g,p),s.fill(),0!==n&&s.stroke()}s.restore()}(r.x(S),P,y),S=gt(w,S+g+u,b?S+k:this.right,t.rtl),function(t,e,i){Ve(s,i.text,t,e+m/2,h,{strikethrough:i.hidden,textAlign:r.textAlign(i.textAlign)})}(r.x(S),P,y),b)f.x+=k+c;else if("string"!=typeof y.text){const t=h.lineHeight;f.y+=ua(y,t)}else f.y+=_})),Oi(this.ctx,t.textDirection)}drawTitle(){const t=this.options,e=t.title,i=wi(e.font),s=Mi(e.padding);if(!e.display)return;const n=Di(t.rtl,this.left,this.width),o=this.ctx,a=e.position,r=i.size/2,l=s.top+r;let h,c=this.left,d=this.width;if(this.isHorizontal())d=Math.max(...this.lineWidths),h=this.top+l,c=ft(t.align,c,this.right-d);else{const e=this.columnSizes.reduce(((t,e)=>Math.max(t,e.height)),0);h=l+ft(t.align,this.top,this.bottom-e-t.labels.padding-this._computeTitleHeight())}const u=ft(a,c,c+d);o.textAlign=n.textAlign(ut(a)),o.textBaseline="middle",o.strokeStyle=e.color,o.fillStyle=e.color,o.font=i.string,Ve(o,e.text,u,h,i)}_computeTitleHeight(){const t=this.options.title,e=wi(t.font),i=Mi(t.padding);return t.display?e.lineHeight+i.height:0}_getLegendItemAt(t,e){let i,s,n;if(tt(t,this.left,this.right)&&tt(e,this.top,this.bottom))for(n=this.legendHitBoxes,i=0;it.chart.options.color,boxWidth:40,padding:10,generateLabels(t){const e=t.data.datasets,{labels:{usePointStyle:i,pointStyle:s,textAlign:n,color:o,useBorderRadius:a,borderRadius:r}}=t.legend.options;return t._getSortedDatasetMetas().map((t=>{const l=t.controller.getStyle(i?0:void 0),h=Mi(l.borderWidth);return{text:e[t.index].label,fillStyle:l.backgroundColor,fontColor:o,hidden:!t.visible,lineCap:l.borderCapStyle,lineDash:l.borderDash,lineDashOffset:l.borderDashOffset,lineJoin:l.borderJoinStyle,lineWidth:(h.width+h.height)/4,strokeStyle:l.borderColor,pointStyle:s||l.pointStyle,rotation:l.rotation,textAlign:n||l.textAlign,borderRadius:a&&(r||l.borderRadius),datasetIndex:t.index}}),this)}},title:{color:t=>t.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:t=>!t.startsWith("on"),labels:{_scriptable:t=>!["generateLabels","filter","sort"].includes(t)}}};class ga extends Bs{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e){const i=this.options;if(this.left=0,this.top=0,!i.display)return void(this.width=this.height=this.right=this.bottom=0);this.width=this.right=t,this.height=this.bottom=e;const s=n(i.text)?i.text.length:1;this._padding=Mi(i.padding);const o=s*wi(i.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=o:this.width=o}isHorizontal(){const t=this.options.position;return"top"===t||"bottom"===t}_drawArgs(t){const{top:e,left:i,bottom:s,right:n,options:o}=this,a=o.align;let r,l,h,c=0;return this.isHorizontal()?(l=ft(a,i,n),h=e+t,r=n-i):("left"===o.position?(l=i+t,h=ft(a,s,e),c=-.5*C):(l=n-t,h=ft(a,e,s),c=.5*C),r=s-e),{titleX:l,titleY:h,maxWidth:r,rotation:c}}draw(){const t=this.ctx,e=this.options;if(!e.display)return;const i=wi(e.font),s=i.lineHeight/2+this._padding.top,{titleX:n,titleY:o,maxWidth:a,rotation:r}=this._drawArgs(s);Ve(t,e.text,0,0,i,{color:e.color,maxWidth:a,rotation:r,textAlign:ut(e.align),textBaseline:"middle",translation:[n,o]})}}var pa={id:"title",_element:ga,start(t,e,i){!function(t,e){const i=new ga({ctx:t.ctx,options:e,chart:t});ns.configure(t,i,e),ns.addBox(t,i),t.titleBlock=i}(t,i)},stop(t){const e=t.titleBlock;ns.removeBox(t,e),delete t.titleBlock},beforeUpdate(t,e,i){const s=t.titleBlock;ns.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const ma=new WeakMap;var ba={id:"subtitle",start(t,e,i){const s=new ga({ctx:t.ctx,options:i,chart:t});ns.configure(t,s,i),ns.addBox(t,s),ma.set(t,s)},stop(t){ns.removeBox(t,ma.get(t)),ma.delete(t)},beforeUpdate(t,e,i){const s=ma.get(t);ns.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"normal"},fullSize:!0,padding:0,position:"top",text:"",weight:1500},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const xa={average(t){if(!t.length)return!1;let e,i,s=0,n=0,o=0;for(e=0,i=t.length;e-1?t.split("\n"):t}function va(t,e){const{element:i,datasetIndex:s,index:n}=e,o=t.getDatasetMeta(s).controller,{label:a,value:r}=o.getLabelAndValue(n);return{chart:t,label:a,parsed:o.getParsed(n),raw:t.data.datasets[s].data[n],formattedValue:r,dataset:o.getDataset(),dataIndex:n,datasetIndex:s,element:i}}function Ma(t,e){const i=t.chart.ctx,{body:s,footer:n,title:o}=t,{boxWidth:a,boxHeight:r}=e,l=wi(e.bodyFont),h=wi(e.titleFont),c=wi(e.footerFont),d=o.length,f=n.length,g=s.length,p=Mi(e.padding);let m=p.height,b=0,x=s.reduce(((t,e)=>t+e.before.length+e.lines.length+e.after.length),0);if(x+=t.beforeBody.length+t.afterBody.length,d&&(m+=d*h.lineHeight+(d-1)*e.titleSpacing+e.titleMarginBottom),x){m+=g*(e.displayColors?Math.max(r,l.lineHeight):l.lineHeight)+(x-g)*l.lineHeight+(x-1)*e.bodySpacing}f&&(m+=e.footerMarginTop+f*c.lineHeight+(f-1)*e.footerSpacing);let _=0;const y=function(t){b=Math.max(b,i.measureText(t).width+_)};return i.save(),i.font=h.string,u(t.title,y),i.font=l.string,u(t.beforeBody.concat(t.afterBody),y),_=e.displayColors?a+2+e.boxPadding:0,u(s,(t=>{u(t.before,y),u(t.lines,y),u(t.after,y)})),_=0,i.font=c.string,u(t.footer,y),i.restore(),b+=p.width,{width:b,height:m}}function wa(t,e,i,s){const{x:n,width:o}=i,{width:a,chartArea:{left:r,right:l}}=t;let h="center";return"center"===s?h=n<=(r+l)/2?"left":"right":n<=o/2?h="left":n>=a-o/2&&(h="right"),function(t,e,i,s){const{x:n,width:o}=s,a=i.caretSize+i.caretPadding;return"left"===t&&n+o+a>e.width||"right"===t&&n-o-a<0||void 0}(h,t,e,i)&&(h="center"),h}function ka(t,e,i){const s=i.yAlign||e.yAlign||function(t,e){const{y:i,height:s}=e;return it.height-s/2?"bottom":"center"}(t,i);return{xAlign:i.xAlign||e.xAlign||wa(t,e,i,s),yAlign:s}}function Sa(t,e,i,s){const{caretSize:n,caretPadding:o,cornerRadius:a}=t,{xAlign:r,yAlign:l}=i,h=n+o,{topLeft:c,topRight:d,bottomLeft:u,bottomRight:f}=vi(a);let g=function(t,e){let{x:i,width:s}=t;return"right"===e?i-=s:"center"===e&&(i-=s/2),i}(e,r);const p=function(t,e,i){let{y:s,height:n}=t;return"top"===e?s+=i:s-="bottom"===e?n+i:n/2,s}(e,l,h);return"center"===l?"left"===r?g+=h:"right"===r&&(g-=h):"left"===r?g-=Math.max(c,u)+n:"right"===r&&(g+=Math.max(d,f)+n),{x:J(g,0,s.width-e.width),y:J(p,0,s.height-e.height)}}function Pa(t,e,i){const s=Mi(i.padding);return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-s.right:t.x+s.left}function Da(t){return _a([],ya(t))}function Ca(t,e){const i=e&&e.dataset&&e.dataset.tooltip&&e.dataset.tooltip.callbacks;return i?t.override(i):t}const Oa={beforeTitle:e,title(t){if(t.length>0){const e=t[0],i=e.chart.data.labels,s=i?i.length:0;if(this&&this.options&&"dataset"===this.options.mode)return e.dataset.label||"";if(e.label)return e.label;if(s>0&&e.dataIndex{const e={before:[],lines:[],after:[]},n=Ca(i,t);_a(e.before,ya(Aa(n,"beforeLabel",this,t))),_a(e.lines,Aa(n,"label",this,t)),_a(e.after,ya(Aa(n,"afterLabel",this,t))),s.push(e)})),s}getAfterBody(t,e){return Da(Aa(e.callbacks,"afterBody",this,t))}getFooter(t,e){const{callbacks:i}=e,s=Aa(i,"beforeFooter",this,t),n=Aa(i,"footer",this,t),o=Aa(i,"afterFooter",this,t);let a=[];return a=_a(a,ya(s)),a=_a(a,ya(n)),a=_a(a,ya(o)),a}_createItems(t){const e=this._active,i=this.chart.data,s=[],n=[],o=[];let a,r,l=[];for(a=0,r=e.length;at.filter(e,s,n,i)))),t.itemSort&&(l=l.sort(((e,s)=>t.itemSort(e,s,i)))),u(l,(e=>{const i=Ca(t.callbacks,e);s.push(Aa(i,"labelColor",this,e)),n.push(Aa(i,"labelPointStyle",this,e)),o.push(Aa(i,"labelTextColor",this,e))})),this.labelColors=s,this.labelPointStyles=n,this.labelTextColors=o,this.dataPoints=l,l}update(t,e){const i=this.options.setContext(this.getContext()),s=this._active;let n,o=[];if(s.length){const t=xa[i.position].call(this,s,this._eventPosition);o=this._createItems(i),this.title=this.getTitle(o,i),this.beforeBody=this.getBeforeBody(o,i),this.body=this.getBody(o,i),this.afterBody=this.getAfterBody(o,i),this.footer=this.getFooter(o,i);const e=this._size=Ma(this,i),a=Object.assign({},t,e),r=ka(this.chart,i,a),l=Sa(i,a,r,this.chart);this.xAlign=r.xAlign,this.yAlign=r.yAlign,n={opacity:1,x:l.x,y:l.y,width:e.width,height:e.height,caretX:t.x,caretY:t.y}}else 0!==this.opacity&&(n={opacity:0});this._tooltipItems=o,this.$context=void 0,n&&this._resolveAnimations().update(this,n),t&&i.external&&i.external.call(this,{chart:this.chart,tooltip:this,replay:e})}drawCaret(t,e,i,s){const n=this.getCaretPosition(t,i,s);e.lineTo(n.x1,n.y1),e.lineTo(n.x2,n.y2),e.lineTo(n.x3,n.y3)}getCaretPosition(t,e,i){const{xAlign:s,yAlign:n}=this,{caretSize:o,cornerRadius:a}=i,{topLeft:r,topRight:l,bottomLeft:h,bottomRight:c}=vi(a),{x:d,y:u}=t,{width:f,height:g}=e;let p,m,b,x,_,y;return"center"===n?(_=u+g/2,"left"===s?(p=d,m=p-o,x=_+o,y=_-o):(p=d+f,m=p+o,x=_-o,y=_+o),b=p):(m="left"===s?d+Math.max(r,h)+o:"right"===s?d+f-Math.max(l,c)-o:this.caretX,"top"===n?(x=u,_=x-o,p=m-o,b=m+o):(x=u+g,_=x+o,p=m+o,b=m-o),y=x),{x1:p,x2:m,x3:b,y1:x,y2:_,y3:y}}drawTitle(t,e,i){const s=this.title,n=s.length;let o,a,r;if(n){const l=Di(i.rtl,this.x,this.width);for(t.x=Pa(this,i.titleAlign,i),e.textAlign=l.textAlign(i.titleAlign),e.textBaseline="middle",o=wi(i.titleFont),a=i.titleSpacing,e.fillStyle=i.titleColor,e.font=o.string,r=0;r0!==t))?(t.beginPath(),t.fillStyle=n.multiKeyBackground,We(t,{x:e,y:p,w:h,h:l,radius:r}),t.fill(),t.stroke(),t.fillStyle=a.backgroundColor,t.beginPath(),We(t,{x:i,y:p+1,w:h-2,h:l-2,radius:r}),t.fill()):(t.fillStyle=n.multiKeyBackground,t.fillRect(e,p,h,l),t.strokeRect(e,p,h,l),t.fillStyle=a.backgroundColor,t.fillRect(i,p+1,h-2,l-2))}t.fillStyle=this.labelTextColors[i]}drawBody(t,e,i){const{body:s}=this,{bodySpacing:n,bodyAlign:o,displayColors:a,boxHeight:r,boxWidth:l,boxPadding:h}=i,c=wi(i.bodyFont);let d=c.lineHeight,f=0;const g=Di(i.rtl,this.x,this.width),p=function(i){e.fillText(i,g.x(t.x+f),t.y+d/2),t.y+=d+n},m=g.textAlign(o);let b,x,_,y,v,M,w;for(e.textAlign=o,e.textBaseline="middle",e.font=c.string,t.x=Pa(this,m,i),e.fillStyle=i.bodyColor,u(this.beforeBody,p),f=a&&"right"!==m?"center"===o?l/2+h:l+2+h:0,y=0,M=s.length;y0&&e.stroke()}_updateAnimationTarget(t){const e=this.chart,i=this.$animations,s=i&&i.x,n=i&&i.y;if(s||n){const i=xa[t.position].call(this,this._active,this._eventPosition);if(!i)return;const o=this._size=Ma(this,t),a=Object.assign({},i,this._size),r=ka(e,t,a),l=Sa(t,a,r,e);s._to===l.x&&n._to===l.y||(this.xAlign=r.xAlign,this.yAlign=r.yAlign,this.width=o.width,this.height=o.height,this.caretX=i.x,this.caretY=i.y,this._resolveAnimations().update(this,l))}}_willRender(){return!!this.opacity}draw(t){const e=this.options.setContext(this.getContext());let i=this.opacity;if(!i)return;this._updateAnimationTarget(e);const s={width:this.width,height:this.height},n={x:this.x,y:this.y};i=Math.abs(i)<.001?0:i;const o=Mi(e.padding),a=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;e.enabled&&a&&(t.save(),t.globalAlpha=i,this.drawBackground(n,t,s,e),Ci(t,e.textDirection),n.y+=o.top,this.drawTitle(n,t,e),this.drawBody(n,t,e),this.drawFooter(n,t,e),Oi(t,e.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,e){const i=this._active,s=t.map((({datasetIndex:t,index:e})=>{const i=this.chart.getDatasetMeta(t);if(!i)throw new Error("Cannot find a dataset at index "+t);return{datasetIndex:t,element:i.data[e],index:e}})),n=!f(i,s),o=this._positionChanged(s,e);(n||o)&&(this._active=s,this._eventPosition=e,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(t,e,i=!0){if(e&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;const s=this.options,n=this._active||[],o=this._getActiveElements(t,n,e,i),a=this._positionChanged(o,t),r=e||!f(o,n)||a;return r&&(this._active=o,(s.enabled||s.external)&&(this._eventPosition={x:t.x,y:t.y},this.update(!0,e))),r}_getActiveElements(t,e,i,s){const n=this.options;if("mouseout"===t.type)return[];if(!s)return e;const o=this.chart.getElementsAtEventForMode(t,n.mode,n,i);return n.reverse&&o.reverse(),o}_positionChanged(t,e){const{caretX:i,caretY:s,options:n}=this,o=xa[n.position].call(this,t,e);return!1!==o&&(i!==o.x||s!==o.y)}}var La={id:"tooltip",_element:Ta,positioners:xa,afterInit(t,e,i){i&&(t.tooltip=new Ta({chart:t,options:i}))},beforeUpdate(t,e,i){t.tooltip&&t.tooltip.initialize(i)},reset(t,e,i){t.tooltip&&t.tooltip.initialize(i)},afterDraw(t){const e=t.tooltip;if(e&&e._willRender()){const i={tooltip:e};if(!1===t.notifyPlugins("beforeTooltipDraw",{...i,cancelable:!0}))return;e.draw(t.ctx),t.notifyPlugins("afterTooltipDraw",i)}},afterEvent(t,e){if(t.tooltip){const i=e.replay;t.tooltip.handleEvent(e.event,i,e.inChartArea)&&(e.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(t,e)=>e.bodyFont.size,boxWidth:(t,e)=>e.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:Oa},defaultRoutes:{bodyFont:"font",footerFont:"font",titleFont:"font"},descriptors:{_scriptable:t=>"filter"!==t&&"itemSort"!==t&&"external"!==t,_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]};return wn.register(Vn,zo,oo,t),wn.helpers={...Vi},wn._adapters=Dn,wn.Animation=Ss,wn.Animations=Ps,wn.animator=xt,wn.controllers=Js.controllers.items,wn.DatasetController=Vs,wn.Element=Bs,wn.elements=oo,wn.Interaction=Yi,wn.layouts=ns,wn.platforms=Ms,wn.Scale=Ks,wn.Ticks=ae,Object.assign(wn,Vn,zo,oo,t,Ms),wn.Chart=wn,"undefined"!=typeof window&&(window.Chart=wn),wn})); //# sourceMappingURL=chart.umd.js.map lib/IPv6/IPv6.php000064400000012756147577535410007430 0ustar00 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ // Include only if necessary if(!class_exists('Math_BigInteger')){ require_once 'BigInteger.php'; } /** * Converts human readable representation to a 128 bit int * which can be stored in MySQL using DECIMAL(39,0). * * Requires PHP to be compiled with IPv6 support. * This could be made to work without IPv6 support but * I don't think there would be much use for it if PHP * doesn't support IPv6. * * @param string $ip IPv4 or IPv6 address to convert * @return string 128 bit string that can be used with DECIMNAL(39,0) or false */ if(!function_exists('inet_ptoi')) { function inet_ptoi($ip) { // make sure it is an ip if (filter_var($ip, FILTER_VALIDATE_IP) === false) return false; $parts = unpack('N*', inet_pton($ip)); // fix IPv4 if (strpos($ip, '.') !== false) $parts = array(1=>0, 2=>0, 3=>0, 4=>$parts[1]); foreach ($parts as &$part) { // convert any unsigned ints to signed from unpack. // this should be OK as it will be a PHP float not an int if ($part < 0) $part += 4294967296; } // use BCMath if the extension exists if (function_exists('bcadd')) { $decimal = $parts[4]; $decimal = bcadd($decimal, bcmul($parts[3], '4294967296')); $decimal = bcadd($decimal, bcmul($parts[2], '18446744073709551616')); $decimal = bcadd($decimal, bcmul($parts[1], '79228162514264337593543950336')); } // otherwise use the pure PHP BigInteger else { $decimal = new Math_BigInteger($parts[4]); $part3 = new Math_BigInteger($parts[3]); $part2 = new Math_BigInteger($parts[2]); $part1 = new Math_BigInteger($parts[1]); $decimal = $decimal->add($part3->multiply(new Math_BigInteger('4294967296'))); $decimal = $decimal->add($part2->multiply(new Math_BigInteger('18446744073709551616'))); $decimal = $decimal->add($part1->multiply(new Math_BigInteger('79228162514264337593543950336'))); $decimal = $decimal->toString(); } return $decimal; } } /** * Converts a 128 bit int to a human readable representation. * * Requires PHP to be compiled with IPv6 support. * This could be made to work without IPv6 support but * I don't think there would be much use for it if PHP * doesn't support IPv6. * * @param string $decimal 128 bit int * @return string IPv4 or IPv6 */ if(!function_exists('inet_itop')) { function inet_itop($decimal) { $parts = array(); // use BCMath if the extension exists if (function_exists('bcadd')) { $parts[1] = bcdiv($decimal, '79228162514264337593543950336', 0); $decimal = bcsub($decimal, bcmul($parts[1], '79228162514264337593543950336')); $parts[2] = bcdiv($decimal, '18446744073709551616', 0); $decimal = bcsub($decimal, bcmul($parts[2], '18446744073709551616')); $parts[3] = bcdiv($decimal, '4294967296', 0); $decimal = bcsub($decimal, bcmul($parts[3], '4294967296')); $parts[4] = $decimal; } // otherwise use the pure PHP BigInteger else { $decimal = new Math_BigInteger($decimal); list($parts[1],) = $decimal->divide(new Math_BigInteger('79228162514264337593543950336')); $decimal = $decimal->subtract($parts[1]->multiply(new Math_BigInteger('79228162514264337593543950336'))); list($parts[2],) = $decimal->divide(new Math_BigInteger('18446744073709551616')); $decimal = $decimal->subtract($parts[2]->multiply(new Math_BigInteger('18446744073709551616'))); list($parts[3],) = $decimal->divide(new Math_BigInteger('4294967296')); $decimal = $decimal->subtract($parts[3]->multiply(new Math_BigInteger('4294967296'))); $parts[4] = $decimal; $parts[1] = $parts[1]->toString(); $parts[2] = $parts[2]->toString(); $parts[3] = $parts[3]->toString(); $parts[4] = $parts[4]->toString(); } foreach ($parts as &$part) { // convert any signed ints to unsigned for pack // this should be fine as it will be treated as a float if ($part > 2147483647) $part -= 4294967296; } $ip = inet_ntop(pack('N4', $parts[1], $parts[2], $parts[3], $parts[4])); // fix IPv4 by removing :: from the beginning if (strpos($ip, '.') !== false) return substr($ip, 2); return $ip; } } // Example // // echo inet_ptoi('::FFFF:FFFF:FFFF:FFFF') . "\n"; // echo inet_ptoi('::FFFF:FFFF') . "\n"; // echo inet_ptoi('255.255.255.255') . "\n"; // echo inet_itop(inet_ptoi('::FFFF:FFFF:FFFF:FFFF')) . "\n"; // echo inet_itop(inet_ptoi('::FFFF:FFFF')) . "\n"; /* Output: 18446744073709551615 4294967295 4294967295 ::ffff:ffff:ffff:ffff 255.255.255.255 */ lib/IPv6/BigInteger.php000064400000347502147577535410010663 0ustar00> and << cannot be used, nor can the modulo operator %, * which only supports integers. Although this fact will slow this library down, the fact that such a high * base is being used should more than compensate. * * When PHP version 6 is officially released, we'll be able to use 64-bit integers. This should, once again, * allow bitwise operators, and will increase the maximum possible base to 2**31 (or 2**62 for addition / * subtraction). * * Numbers are stored in {@link http://en.wikipedia.org/wiki/Endianness little endian} format. ie. * (new Math_BigInteger(pow(2, 26)))->value = array(0, 1) * * Useful resources are as follows: * * - {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf Handbook of Applied Cryptography (HAC)} * - {@link http://math.libtomcrypt.com/files/tommath.pdf Multi-Precision Math (MPM)} * - Java's BigInteger classes. See /j2se/src/share/classes/java/math in jdk-1_5_0-src-jrl.zip * * Here's an example of how to use this library: * * add($b); * * echo $c->toString(); // outputs 5 * ?> * * * LICENSE: This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * * @category Math * @package Math_BigInteger * @author Jim Wigginton * @copyright MMVI Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version $Id: BigInteger.php,v 1.33 2010/03/22 22:32:03 terrafrost Exp $ * @link http://pear.php.net/package/Math_BigInteger */ /**#@+ * Reduction constants * * @access private * @see Math_BigInteger::_reduce() */ /** * @see Math_BigInteger::_montgomery() * @see Math_BigInteger::_prepMontgomery() */ define('MATH_BIGINTEGER_MONTGOMERY', 0); /** * @see Math_BigInteger::_barrett() */ define('MATH_BIGINTEGER_BARRETT', 1); /** * @see Math_BigInteger::_mod2() */ define('MATH_BIGINTEGER_POWEROF2', 2); /** * @see Math_BigInteger::_remainder() */ define('MATH_BIGINTEGER_CLASSIC', 3); /** * @see Math_BigInteger::__clone() */ define('MATH_BIGINTEGER_NONE', 4); /**#@-*/ /**#@+ * Array constants * * Rather than create a thousands and thousands of new Math_BigInteger objects in repeated function calls to add() and * multiply() or whatever, we'll just work directly on arrays, taking them in as parameters and returning them. * * @access private */ /** * $result[MATH_BIGINTEGER_VALUE] contains the value. */ define('MATH_BIGINTEGER_VALUE', 0); /** * $result[MATH_BIGINTEGER_SIGN] contains the sign. */ define('MATH_BIGINTEGER_SIGN', 1); /**#@-*/ /**#@+ * @access private * @see Math_BigInteger::_montgomery() * @see Math_BigInteger::_barrett() */ /** * Cache constants * * $cache[MATH_BIGINTEGER_VARIABLE] tells us whether or not the cached data is still valid. */ define('MATH_BIGINTEGER_VARIABLE', 0); /** * $cache[MATH_BIGINTEGER_DATA] contains the cached data. */ define('MATH_BIGINTEGER_DATA', 1); /**#@-*/ /**#@+ * Mode constants. * * @access private * @see Math_BigInteger::Math_BigInteger() */ /** * To use the pure-PHP implementation */ define('MATH_BIGINTEGER_MODE_INTERNAL', 1); /** * To use the BCMath library * * (if enabled; otherwise, the internal implementation will be used) */ define('MATH_BIGINTEGER_MODE_BCMATH', 2); /** * To use the GMP library * * (if present; otherwise, either the BCMath or the internal implementation will be used) */ define('MATH_BIGINTEGER_MODE_GMP', 3); /**#@-*/ /** * The largest digit that may be used in addition / subtraction * * (we do pow(2, 52) instead of using 4503599627370496, directly, because some PHP installations * will truncate 4503599627370496) * * @access private */ define('MATH_BIGINTEGER_MAX_DIGIT52', pow(2, 52)); /** * Karatsuba Cutoff * * At what point do we switch between Karatsuba multiplication and schoolbook long multiplication? * * @access private */ define('MATH_BIGINTEGER_KARATSUBA_CUTOFF', 25); /** * Pure-PHP arbitrary precision integer arithmetic library. Supports base-2, base-10, base-16, and base-256 * numbers. * * @author Jim Wigginton * @version 1.0.0RC4 * @access public * @package Math_BigInteger */ class Math_BigInteger { /** * Holds the BigInteger's value. * * @var Array * @access private */ var $value; /** * Holds the BigInteger's magnitude. * * @var Boolean * @access private */ var $is_negative = false; /** * Random number generator function * * @see setRandomGenerator() * @access private */ var $generator = 'mt_rand'; /** * Precision * * @see setPrecision() * @access private */ var $precision = -1; /** * Precision Bitmask * * @see setPrecision() * @access private */ var $bitmask = false; /** * Mode independant value used for serialization. * * If the bcmath or gmp extensions are installed $this->value will be a non-serializable resource, hence the need for * a variable that'll be serializable regardless of whether or not extensions are being used. Unlike $this->value, * however, $this->hex is only calculated when $this->__sleep() is called. * * @see __sleep() * @see __wakeup() * @var String * @access private */ var $hex; /** * Converts base-2, base-10, base-16, and binary strings (eg. base-256) to BigIntegers. * * If the second parameter - $base - is negative, then it will be assumed that the number's are encoded using * two's compliment. The sole exception to this is -10, which is treated the same as 10 is. * * Here's an example: * * toString(); // outputs 50 * ?> * * * @param optional $x base-10 number or base-$base number if $base set. * @param optional integer $base * @return Math_BigInteger * @access public */ function __construct($x = 0, $base = 10) { if ( !defined('MATH_BIGINTEGER_MODE') ) { switch (true) { case extension_loaded('gmp'): define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_GMP); break; case extension_loaded('bcmath'): define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_BCMATH); break; default: define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_INTERNAL); } } switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: if (is_resource($x) && get_resource_type($x) == 'GMP integer') { $this->value = $x; return; } $this->value = gmp_init(0); break; case MATH_BIGINTEGER_MODE_BCMATH: $this->value = '0'; break; default: $this->value = array(); } if (empty($x)) { return; } switch ($base) { case -256: if (ord($x[0]) & 0x80) { $x = ~$x; $this->is_negative = true; } case 256: switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $sign = $this->is_negative ? '-' : ''; $this->value = gmp_init($sign . '0x' . bin2hex($x)); break; case MATH_BIGINTEGER_MODE_BCMATH: // round $len to the nearest 4 (thanks, DavidMJ!) $len = (strlen($x) + 3) & 0xFFFFFFFC; $x = str_pad($x, $len, chr(0), STR_PAD_LEFT); for ($i = 0; $i < $len; $i+= 4) { $this->value = bcmul($this->value, '4294967296', 0); // 4294967296 == 2**32 $this->value = bcadd($this->value, 0x1000000 * ord($x[$i]) + ((ord($x[$i + 1]) << 16) | (ord($x[$i + 2]) << 8) | ord($x[$i + 3])), 0); } if ($this->is_negative) { $this->value = '-' . $this->value; } break; // converts a base-2**8 (big endian / msb) number to base-2**26 (little endian / lsb) default: while (strlen($x)) { $this->value[] = $this->_bytes2int($this->_base256_rshift($x, 26)); } } if ($this->is_negative) { if (MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL) { $this->is_negative = false; } $temp = $this->add(new Math_BigInteger('-1')); $this->value = $temp->value; } break; case 16: case -16: if ($base > 0 && $x[0] == '-') { $this->is_negative = true; $x = substr($x, 1); } $x = preg_replace('#^(?:0x)?([A-Fa-f0-9]*).*#', '$1', $x); $is_negative = false; if ($base < 0 && hexdec($x[0]) >= 8) { $this->is_negative = $is_negative = true; $x = bin2hex(~pack('H*', $x)); } switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = $this->is_negative ? '-0x' . $x : '0x' . $x; $this->value = gmp_init($temp); $this->is_negative = false; break; case MATH_BIGINTEGER_MODE_BCMATH: $x = ( strlen($x) & 1 ) ? '0' . $x : $x; $temp = new Math_BigInteger(pack('H*', $x), 256); $this->value = $this->is_negative ? '-' . $temp->value : $temp->value; $this->is_negative = false; break; default: $x = ( strlen($x) & 1 ) ? '0' . $x : $x; $temp = new Math_BigInteger(pack('H*', $x), 256); $this->value = $temp->value; } if ($is_negative) { $temp = $this->add(new Math_BigInteger('-1')); $this->value = $temp->value; } break; case 10: case -10: $x = preg_replace('#^(-?[0-9]*).*#', '$1', $x); switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $this->value = gmp_init($x); break; case MATH_BIGINTEGER_MODE_BCMATH: // explicitly casting $x to a string is necessary, here, since doing $x[0] on -1 yields different // results then doing it on '-1' does (modInverse does $x[0]) $this->value = (string) $x; break; default: $temp = new Math_BigInteger(); // array(10000000) is 10**7 in base-2**26. 10**7 is the closest to 2**26 we can get without passing it. $multiplier = new Math_BigInteger(); $multiplier->value = array(10000000); if ($x[0] == '-') { $this->is_negative = true; $x = substr($x, 1); } $x = str_pad($x, strlen($x) + (6 * strlen($x)) % 7, 0, STR_PAD_LEFT); while (strlen($x)) { $temp = $temp->multiply($multiplier); $temp = $temp->add(new Math_BigInteger($this->_int2bytes(substr($x, 0, 7)), 256)); $x = substr($x, 7); } $this->value = $temp->value; } break; case 2: // base-2 support originally implemented by Lluis Pamies - thanks! case -2: if ($base > 0 && $x[0] == '-') { $this->is_negative = true; $x = substr($x, 1); } $x = preg_replace('#^([01]*).*#', '$1', $x); $x = str_pad($x, strlen($x) + (3 * strlen($x)) % 4, 0, STR_PAD_LEFT); $str = '0x'; while (strlen($x)) { $part = substr($x, 0, 4); $str.= dechex(bindec($part)); $x = substr($x, 4); } if ($this->is_negative) { $str = '-' . $str; } $temp = new Math_BigInteger($str, 8 * $base); // ie. either -16 or +16 $this->value = $temp->value; $this->is_negative = $temp->is_negative; break; default: // base not supported, so we'll let $this == 0 } } /** * This function exists to maintain backwards compatibility with older code * * @param int $x base-10 number or base-$base number if $base set. * @param int $base Number base */ public function Math_BigInteger($x = 0, $base = 10) { self::__construct($x, $base); } /** * Converts a BigInteger to a byte string (eg. base-256). * * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're * saved as two's compliment. * * Here's an example: * * toBytes(); // outputs chr(65) * ?> * * * @param Boolean $twos_compliment * @return String * @access public * @internal Converts a base-2**26 number to base-2**8 */ function toBytes($twos_compliment = false) { if ($twos_compliment) { $comparison = $this->compare(new Math_BigInteger()); if ($comparison == 0) { return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; } $temp = $comparison < 0 ? $this->add(new Math_BigInteger(1)) : $this->copy(); $bytes = $temp->toBytes(); if (empty($bytes)) { // eg. if the number we're trying to convert is -1 $bytes = chr(0); } if (ord($bytes[0]) & 0x80) { $bytes = chr(0) . $bytes; } return $comparison < 0 ? ~$bytes : $bytes; } switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: if (gmp_cmp($this->value, gmp_init(0)) == 0) { return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; } $temp = gmp_strval(gmp_abs($this->value), 16); $temp = ( strlen($temp) & 1 ) ? '0' . $temp : $temp; $temp = pack('H*', $temp); return $this->precision > 0 ? substr(str_pad($temp, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : ltrim($temp, chr(0)); case MATH_BIGINTEGER_MODE_BCMATH: if ($this->value === '0') { return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; } $value = ''; $current = $this->value; if ($current[0] == '-') { $current = substr($current, 1); } while (bccomp($current, '0', 0) > 0) { $temp = bcmod($current, '16777216'); $value = chr($temp >> 16) . chr($temp >> 8) . chr($temp) . $value; $current = bcdiv($current, '16777216', 0); } return $this->precision > 0 ? substr(str_pad($value, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : ltrim($value, chr(0)); } if (!count($this->value)) { return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; } $result = $this->_int2bytes($this->value[count($this->value) - 1]); $temp = $this->copy(); for ($i = count($temp->value) - 2; $i >= 0; --$i) { $temp->_base256_lshift($result, 26); $result = $result | str_pad($temp->_int2bytes($temp->value[$i]), strlen($result), chr(0), STR_PAD_LEFT); } return $this->precision > 0 ? str_pad(substr($result, -(($this->precision + 7) >> 3)), ($this->precision + 7) >> 3, chr(0), STR_PAD_LEFT) : $result; } /** * Converts a BigInteger to a hex string (eg. base-16)). * * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're * saved as two's compliment. * * Here's an example: * * toHex(); // outputs '41' * ?> * * * @param Boolean $twos_compliment * @return String * @access public * @internal Converts a base-2**26 number to base-2**8 */ function toHex($twos_compliment = false) { return bin2hex($this->toBytes($twos_compliment)); } /** * Converts a BigInteger to a bit string (eg. base-2). * * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're * saved as two's compliment. * * Here's an example: * * toBits(); // outputs '1000001' * ?> * * * @param Boolean $twos_compliment * @return String * @access public * @internal Converts a base-2**26 number to base-2**2 */ function toBits($twos_compliment = false) { $hex = $this->toHex($twos_compliment); $bits = ''; for ($i = 0, $end = strlen($hex) & 0xFFFFFFF8; $i < $end; $i+=8) { $bits.= str_pad(decbin(hexdec(substr($hex, $i, 8))), 32, '0', STR_PAD_LEFT); } if ($end != strlen($hex)) { // hexdec('') == 0 $bits.= str_pad(decbin(hexdec(substr($hex, $end))), strlen($hex) & 7, '0', STR_PAD_LEFT); } return $this->precision > 0 ? substr($bits, -$this->precision) : ltrim($bits, '0'); } /** * Converts a BigInteger to a base-10 number. * * Here's an example: * * toString(); // outputs 50 * ?> * * * @return String * @access public * @internal Converts a base-2**26 number to base-10**7 (which is pretty much base-10) */ function toString() { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: return gmp_strval($this->value); case MATH_BIGINTEGER_MODE_BCMATH: if ($this->value === '0') { return '0'; } return ltrim($this->value, '0'); } if (!count($this->value)) { return '0'; } $temp = $this->copy(); $temp->is_negative = false; $divisor = new Math_BigInteger(); $divisor->value = array(10000000); // eg. 10**7 $result = ''; while (count($temp->value)) { list($temp, $mod) = $temp->divide($divisor); $result = str_pad(isset($mod->value[0]) ? $mod->value[0] : '', 7, '0', STR_PAD_LEFT) . $result; } $result = ltrim($result, '0'); if (empty($result)) { $result = '0'; } if ($this->is_negative) { $result = '-' . $result; } return $result; } /** * Copy an object * * PHP5 passes objects by reference while PHP4 passes by value. As such, we need a function to guarantee * that all objects are passed by value, when appropriate. More information can be found here: * * {@link http://php.net/language.oop5.basic#51624} * * @access public * @see __clone() * @return Math_BigInteger */ function copy() { $temp = new Math_BigInteger(); $temp->value = $this->value; $temp->is_negative = $this->is_negative; $temp->generator = $this->generator; $temp->precision = $this->precision; $temp->bitmask = $this->bitmask; return $temp; } /** * __toString() magic method * * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call * toString(). * * @access public * @internal Implemented per a suggestion by Techie-Michael - thanks! */ function __toString() { return $this->toString(); } /** * __clone() magic method * * Although you can call Math_BigInteger::__toString() directly in PHP5, you cannot call Math_BigInteger::__clone() * directly in PHP5. You can in PHP4 since it's not a magic method, but in PHP5, you have to call it by using the PHP5 * only syntax of $y = clone $x. As such, if you're trying to write an application that works on both PHP4 and PHP5, * call Math_BigInteger::copy(), instead. * * @access public * @see copy() * @return Math_BigInteger */ function __clone() { return $this->copy(); } /** * __sleep() magic method * * Will be called, automatically, when serialize() is called on a Math_BigInteger object. * * @see __wakeup() * @access public */ function __sleep() { $this->hex = $this->toHex(true); $vars = array('hex'); if ($this->generator != 'mt_rand') { $vars[] = 'generator'; } if ($this->precision > 0) { $vars[] = 'precision'; } return $vars; } /** * __wakeup() magic method * * Will be called, automatically, when unserialize() is called on a Math_BigInteger object. * * @see __sleep() * @access public */ function __wakeup() { $temp = new Math_BigInteger($this->hex, -16); $this->value = $temp->value; $this->is_negative = $temp->is_negative; $this->setRandomGenerator($this->generator); if ($this->precision > 0) { // recalculate $this->bitmask $this->setPrecision($this->precision); } } /** * Adds two BigIntegers. * * Here's an example: * * add($b); * * echo $c->toString(); // outputs 30 * ?> * * * @param Math_BigInteger $y * @return Math_BigInteger * @access public * @internal Performs base-2**52 addition */ function add($y) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = new Math_BigInteger(); $temp->value = gmp_add($this->value, $y->value); return $this->_normalize($temp); case MATH_BIGINTEGER_MODE_BCMATH: $temp = new Math_BigInteger(); $temp->value = bcadd($this->value, $y->value, 0); return $this->_normalize($temp); } $temp = $this->_add($this->value, $this->is_negative, $y->value, $y->is_negative); $result = new Math_BigInteger(); $result->value = $temp[MATH_BIGINTEGER_VALUE]; $result->is_negative = $temp[MATH_BIGINTEGER_SIGN]; return $this->_normalize($result); } /** * Performs addition. * * @param Array $x_value * @param Boolean $x_negative * @param Array $y_value * @param Boolean $y_negative * @return Array * @access private */ function _add($x_value, $x_negative, $y_value, $y_negative) { $x_size = count($x_value); $y_size = count($y_value); if ($x_size == 0) { return array( MATH_BIGINTEGER_VALUE => $y_value, MATH_BIGINTEGER_SIGN => $y_negative ); } else if ($y_size == 0) { return array( MATH_BIGINTEGER_VALUE => $x_value, MATH_BIGINTEGER_SIGN => $x_negative ); } // subtract, if appropriate if ( $x_negative != $y_negative ) { if ( $x_value == $y_value ) { return array( MATH_BIGINTEGER_VALUE => array(), MATH_BIGINTEGER_SIGN => false ); } $temp = $this->_subtract($x_value, false, $y_value, false); $temp[MATH_BIGINTEGER_SIGN] = $this->_compare($x_value, false, $y_value, false) > 0 ? $x_negative : $y_negative; return $temp; } if ($x_size < $y_size) { $size = $x_size; $value = $y_value; } else { $size = $y_size; $value = $x_value; } $value[] = 0; // just in case the carry adds an extra digit $carry = 0; for ($i = 0, $j = 1; $j < $size; $i+=2, $j+=2) { $sum = $x_value[$j] * 0x4000000 + $x_value[$i] + $y_value[$j] * 0x4000000 + $y_value[$i] + $carry; $carry = $sum >= MATH_BIGINTEGER_MAX_DIGIT52; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 $sum = $carry ? $sum - MATH_BIGINTEGER_MAX_DIGIT52 : $sum; $temp = (int) ($sum / 0x4000000); $value[$i] = (int) ($sum - 0x4000000 * $temp); // eg. a faster alternative to fmod($sum, 0x4000000) $value[$j] = $temp; } if ($j == $size) { // ie. if $y_size is odd $sum = $x_value[$i] + $y_value[$i] + $carry; $carry = $sum >= 0x4000000; $value[$i] = $carry ? $sum - 0x4000000 : $sum; ++$i; // ie. let $i = $j since we've just done $value[$i] } if ($carry) { for (; $value[$i] == 0x3FFFFFF; ++$i) { $value[$i] = 0; } ++$value[$i]; } return array( MATH_BIGINTEGER_VALUE => $this->_trim($value), MATH_BIGINTEGER_SIGN => $x_negative ); } /** * Subtracts two BigIntegers. * * Here's an example: * * subtract($b); * * echo $c->toString(); // outputs -10 * ?> * * * @param Math_BigInteger $y * @return Math_BigInteger * @access public * @internal Performs base-2**52 subtraction */ function subtract($y) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = new Math_BigInteger(); $temp->value = gmp_sub($this->value, $y->value); return $this->_normalize($temp); case MATH_BIGINTEGER_MODE_BCMATH: $temp = new Math_BigInteger(); $temp->value = bcsub($this->value, $y->value, 0); return $this->_normalize($temp); } $temp = $this->_subtract($this->value, $this->is_negative, $y->value, $y->is_negative); $result = new Math_BigInteger(); $result->value = $temp[MATH_BIGINTEGER_VALUE]; $result->is_negative = $temp[MATH_BIGINTEGER_SIGN]; return $this->_normalize($result); } /** * Performs subtraction. * * @param Array $x_value * @param Boolean $x_negative * @param Array $y_value * @param Boolean $y_negative * @return Array * @access private */ function _subtract($x_value, $x_negative, $y_value, $y_negative) { $x_size = count($x_value); $y_size = count($y_value); if ($x_size == 0) { return array( MATH_BIGINTEGER_VALUE => $y_value, MATH_BIGINTEGER_SIGN => !$y_negative ); } else if ($y_size == 0) { return array( MATH_BIGINTEGER_VALUE => $x_value, MATH_BIGINTEGER_SIGN => $x_negative ); } // add, if appropriate (ie. -$x - +$y or +$x - -$y) if ( $x_negative != $y_negative ) { $temp = $this->_add($x_value, false, $y_value, false); $temp[MATH_BIGINTEGER_SIGN] = $x_negative; return $temp; } $diff = $this->_compare($x_value, $x_negative, $y_value, $y_negative); if ( !$diff ) { return array( MATH_BIGINTEGER_VALUE => array(), MATH_BIGINTEGER_SIGN => false ); } // switch $x and $y around, if appropriate. if ( (!$x_negative && $diff < 0) || ($x_negative && $diff > 0) ) { $temp = $x_value; $x_value = $y_value; $y_value = $temp; $x_negative = !$x_negative; $x_size = count($x_value); $y_size = count($y_value); } // at this point, $x_value should be at least as big as - if not bigger than - $y_value $carry = 0; for ($i = 0, $j = 1; $j < $y_size; $i+=2, $j+=2) { $sum = $x_value[$j] * 0x4000000 + $x_value[$i] - $y_value[$j] * 0x4000000 - $y_value[$i] - $carry; $carry = $sum < 0; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 $sum = $carry ? $sum + MATH_BIGINTEGER_MAX_DIGIT52 : $sum; $temp = (int) ($sum / 0x4000000); $x_value[$i] = (int) ($sum - 0x4000000 * $temp); $x_value[$j] = $temp; } if ($j == $y_size) { // ie. if $y_size is odd $sum = $x_value[$i] - $y_value[$i] - $carry; $carry = $sum < 0; $x_value[$i] = $carry ? $sum + 0x4000000 : $sum; ++$i; } if ($carry) { for (; !$x_value[$i]; ++$i) { $x_value[$i] = 0x3FFFFFF; } --$x_value[$i]; } return array( MATH_BIGINTEGER_VALUE => $this->_trim($x_value), MATH_BIGINTEGER_SIGN => $x_negative ); } /** * Multiplies two BigIntegers * * Here's an example: * * multiply($b); * * echo $c->toString(); // outputs 200 * ?> * * * @param Math_BigInteger $x * @return Math_BigInteger * @access public */ function multiply($x) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = new Math_BigInteger(); $temp->value = gmp_mul($this->value, $x->value); return $this->_normalize($temp); case MATH_BIGINTEGER_MODE_BCMATH: $temp = new Math_BigInteger(); $temp->value = bcmul($this->value, $x->value, 0); return $this->_normalize($temp); } $temp = $this->_multiply($this->value, $this->is_negative, $x->value, $x->is_negative); $product = new Math_BigInteger(); $product->value = $temp[MATH_BIGINTEGER_VALUE]; $product->is_negative = $temp[MATH_BIGINTEGER_SIGN]; return $this->_normalize($product); } /** * Performs multiplication. * * @param Array $x_value * @param Boolean $x_negative * @param Array $y_value * @param Boolean $y_negative * @return Array * @access private */ function _multiply($x_value, $x_negative, $y_value, $y_negative) { //if ( $x_value == $y_value ) { // return array( // MATH_BIGINTEGER_VALUE => $this->_square($x_value), // MATH_BIGINTEGER_SIGN => $x_sign != $y_value // ); //} $x_length = count($x_value); $y_length = count($y_value); if ( !$x_length || !$y_length ) { // a 0 is being multiplied return array( MATH_BIGINTEGER_VALUE => array(), MATH_BIGINTEGER_SIGN => false ); } return array( MATH_BIGINTEGER_VALUE => min($x_length, $y_length) < 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF ? $this->_trim($this->_regularMultiply($x_value, $y_value)) : $this->_trim($this->_karatsuba($x_value, $y_value)), MATH_BIGINTEGER_SIGN => $x_negative != $y_negative ); } /** * Performs long multiplication on two BigIntegers * * Modeled after 'multiply' in MutableBigInteger.java. * * @param Array $x_value * @param Array $y_value * @return Array * @access private */ function _regularMultiply($x_value, $y_value) { $x_length = count($x_value); $y_length = count($y_value); if ( !$x_length || !$y_length ) { // a 0 is being multiplied return array(); } if ( $x_length < $y_length ) { $temp = $x_value; $x_value = $y_value; $y_value = $temp; $x_length = count($x_value); $y_length = count($y_value); } $product_value = $this->_array_repeat(0, $x_length + $y_length); // the following for loop could be removed if the for loop following it // (the one with nested for loops) initially set $i to 0, but // doing so would also make the result in one set of unnecessary adds, // since on the outermost loops first pass, $product->value[$k] is going // to always be 0 $carry = 0; for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0 $temp = (!empty($x_value[$j]) ? $x_value[$j] : 0) * (!empty($y_value[0]) ? $y_value[0] : 0) + $carry; // $product_value[$k] == 0 $carry = (int) ($temp / 0x4000000); $product_value[$j] = (int) ($temp - 0x4000000 * $carry); } $product_value[$j] = $carry; // the above for loop is what the previous comment was talking about. the // following for loop is the "one with nested for loops" for ($i = 1; $i < $y_length; ++$i) { $carry = 0; for ($j = 0, $k = $i; $j < $x_length; ++$j, ++$k) { $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; $carry = (int) ($temp / 0x4000000); $product_value[$k] = (int) ($temp - 0x4000000 * $carry); } $product_value[$k] = $carry; } return $product_value; } /** * Performs Karatsuba multiplication on two BigIntegers * * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=120 MPM 5.2.3}. * * @param Array $x_value * @param Array $y_value * @return Array * @access private */ function _karatsuba($x_value, $y_value) { $m = min(count($x_value) >> 1, count($y_value) >> 1); if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF) { return $this->_regularMultiply($x_value, $y_value); } $x1 = array_slice($x_value, $m); $x0 = array_slice($x_value, 0, $m); $y1 = array_slice($y_value, $m); $y0 = array_slice($y_value, 0, $m); $z2 = $this->_karatsuba($x1, $y1); $z0 = $this->_karatsuba($x0, $y0); $z1 = $this->_add($x1, false, $x0, false); $temp = $this->_add($y1, false, $y0, false); $z1 = $this->_karatsuba($z1[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_VALUE]); $temp = $this->_add($z2, false, $z0, false); $z1 = $this->_subtract($z1, false, $temp[MATH_BIGINTEGER_VALUE], false); $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); $z1[MATH_BIGINTEGER_VALUE] = array_merge(array_fill(0, $m, 0), $z1[MATH_BIGINTEGER_VALUE]); $xy = $this->_add($z2, false, $z1[MATH_BIGINTEGER_VALUE], $z1[MATH_BIGINTEGER_SIGN]); $xy = $this->_add($xy[MATH_BIGINTEGER_VALUE], $xy[MATH_BIGINTEGER_SIGN], $z0, false); return $xy[MATH_BIGINTEGER_VALUE]; } /** * Performs squaring * * @param Array $x * @return Array * @access private */ function _square($x = false) { return count($x) < 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF ? $this->_trim($this->_baseSquare($x)) : $this->_trim($this->_karatsubaSquare($x)); } /** * Performs traditional squaring on two BigIntegers * * Squaring can be done faster than multiplying a number by itself can be. See * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=7 HAC 14.2.4} / * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=141 MPM 5.3} for more information. * * @param Array $value * @return Array * @access private */ function _baseSquare($value) { if ( empty($value) ) { return array(); } $square_value = $this->_array_repeat(0, 2 * count($value)); for ($i = 0, $max_index = count($value) - 1; $i <= $max_index; ++$i) { $i2 = $i << 1; $temp = $square_value[$i2] + $value[$i] * $value[$i]; $carry = (int) ($temp / 0x4000000); $square_value[$i2] = (int) ($temp - 0x4000000 * $carry); // note how we start from $i+1 instead of 0 as we do in multiplication. for ($j = $i + 1, $k = $i2 + 1; $j <= $max_index; ++$j, ++$k) { $temp = $square_value[$k] + 2 * $value[$j] * $value[$i] + $carry; $carry = (int) ($temp / 0x4000000); $square_value[$k] = (int) ($temp - 0x4000000 * $carry); } // the following line can yield values larger 2**15. at this point, PHP should switch // over to floats. $square_value[$i + $max_index + 1] = $carry; } return $square_value; } /** * Performs Karatsuba "squaring" on two BigIntegers * * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=151 MPM 5.3.4}. * * @param Array $value * @return Array * @access private */ function _karatsubaSquare($value) { $m = count($value) >> 1; if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF) { return $this->_baseSquare($value); } $x1 = array_slice($value, $m); $x0 = array_slice($value, 0, $m); $z2 = $this->_karatsubaSquare($x1); $z0 = $this->_karatsubaSquare($x0); $z1 = $this->_add($x1, false, $x0, false); $z1 = $this->_karatsubaSquare($z1[MATH_BIGINTEGER_VALUE]); $temp = $this->_add($z2, false, $z0, false); $z1 = $this->_subtract($z1, false, $temp[MATH_BIGINTEGER_VALUE], false); $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); $z1[MATH_BIGINTEGER_VALUE] = array_merge(array_fill(0, $m, 0), $z1[MATH_BIGINTEGER_VALUE]); $xx = $this->_add($z2, false, $z1[MATH_BIGINTEGER_VALUE], $z1[MATH_BIGINTEGER_SIGN]); $xx = $this->_add($xx[MATH_BIGINTEGER_VALUE], $xx[MATH_BIGINTEGER_SIGN], $z0, false); return $xx[MATH_BIGINTEGER_VALUE]; } /** * Divides two BigIntegers. * * Returns an array whose first element contains the quotient and whose second element contains the * "common residue". If the remainder would be positive, the "common residue" and the remainder are the * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder * and the divisor (basically, the "common residue" is the first positive modulo). * * Here's an example: * * divide($b); * * echo $quotient->toString(); // outputs 0 * echo "\r\n"; * echo $remainder->toString(); // outputs 10 * ?> * * * @param Math_BigInteger $y * @return Array * @access public * @internal This function is based off of {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=9 HAC 14.20}. */ function divide($y) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $quotient = new Math_BigInteger(); $remainder = new Math_BigInteger(); list($quotient->value, $remainder->value) = gmp_div_qr($this->value, $y->value); if (gmp_sign($remainder->value) < 0) { $remainder->value = gmp_add($remainder->value, gmp_abs($y->value)); } return array($this->_normalize($quotient), $this->_normalize($remainder)); case MATH_BIGINTEGER_MODE_BCMATH: $quotient = new Math_BigInteger(); $remainder = new Math_BigInteger(); $quotient->value = bcdiv($this->value, $y->value, 0); $remainder->value = bcmod($this->value, $y->value); if ($remainder->value[0] == '-') { $remainder->value = bcadd($remainder->value, $y->value[0] == '-' ? substr($y->value, 1) : $y->value, 0); } return array($this->_normalize($quotient), $this->_normalize($remainder)); } if (count($y->value) == 1) { list($q, $r) = $this->_divide_digit($this->value, $y->value[0]); $quotient = new Math_BigInteger(); $remainder = new Math_BigInteger(); $quotient->value = $q; $remainder->value = array($r); $quotient->is_negative = $this->is_negative != $y->is_negative; return array($this->_normalize($quotient), $this->_normalize($remainder)); } static $zero; if ( !isset($zero) ) { $zero = new Math_BigInteger(); } $x = $this->copy(); $y = $y->copy(); $x_sign = $x->is_negative; $y_sign = $y->is_negative; $x->is_negative = $y->is_negative = false; $diff = $x->compare($y); if ( !$diff ) { $temp = new Math_BigInteger(); $temp->value = array(1); $temp->is_negative = $x_sign != $y_sign; return array($this->_normalize($temp), $this->_normalize(new Math_BigInteger())); } if ( $diff < 0 ) { // if $x is negative, "add" $y. if ( $x_sign ) { $x = $y->subtract($x); } return array($this->_normalize(new Math_BigInteger()), $this->_normalize($x)); } // normalize $x and $y as described in HAC 14.23 / 14.24 $msb = $y->value[count($y->value) - 1]; for ($shift = 0; !($msb & 0x2000000); ++$shift) { $msb <<= 1; } $x->_lshift($shift); $y->_lshift($shift); $y_value = &$y->value; $x_max = count($x->value) - 1; $y_max = count($y->value) - 1; $quotient = new Math_BigInteger(); $quotient_value = &$quotient->value; $quotient_value = $this->_array_repeat(0, $x_max - $y_max + 1); static $temp, $lhs, $rhs; if (!isset($temp)) { $temp = new Math_BigInteger(); $lhs = new Math_BigInteger(); $rhs = new Math_BigInteger(); } $temp_value = &$temp->value; $rhs_value = &$rhs->value; // $temp = $y << ($x_max - $y_max-1) in base 2**26 $temp_value = array_merge($this->_array_repeat(0, $x_max - $y_max), $y_value); while ( $x->compare($temp) >= 0 ) { // calculate the "common residue" ++$quotient_value[$x_max - $y_max]; $x = $x->subtract($temp); $x_max = count($x->value) - 1; } for ($i = $x_max; $i >= $y_max + 1; --$i) { $x_value = &$x->value; $x_window = array( isset($x_value[$i]) ? $x_value[$i] : 0, isset($x_value[$i - 1]) ? $x_value[$i - 1] : 0, isset($x_value[$i - 2]) ? $x_value[$i - 2] : 0 ); $y_window = array( $y_value[$y_max], ( $y_max > 0 ) ? $y_value[$y_max - 1] : 0 ); $q_index = $i - $y_max - 1; if ($x_window[0] == $y_window[0]) { $quotient_value[$q_index] = 0x3FFFFFF; } else { $quotient_value[$q_index] = (int) ( ($x_window[0] * 0x4000000 + $x_window[1]) / $y_window[0] ); } $temp_value = array($y_window[1], $y_window[0]); $lhs->value = array($quotient_value[$q_index]); $lhs = $lhs->multiply($temp); $rhs_value = array($x_window[2], $x_window[1], $x_window[0]); while ( $lhs->compare($rhs) > 0 ) { --$quotient_value[$q_index]; $lhs->value = array($quotient_value[$q_index]); $lhs = $lhs->multiply($temp); } $adjust = $this->_array_repeat(0, $q_index); $temp_value = array($quotient_value[$q_index]); $temp = $temp->multiply($y); $temp_value = &$temp->value; $temp_value = array_merge($adjust, $temp_value); $x = $x->subtract($temp); if ($x->compare($zero) < 0) { $temp_value = array_merge($adjust, $y_value); $x = $x->add($temp); --$quotient_value[$q_index]; } $x_max = count($x_value) - 1; } // unnormalize the remainder $x->_rshift($shift); $quotient->is_negative = $x_sign != $y_sign; // calculate the "common residue", if appropriate if ( $x_sign ) { $y->_rshift($shift); $x = $y->subtract($x); } return array($this->_normalize($quotient), $this->_normalize($x)); } /** * Divides a BigInteger by a regular integer * * abc / x = a00 / x + b0 / x + c / x * * @param Array $dividend * @param Array $divisor * @return Array * @access private */ function _divide_digit($dividend, $divisor) { $carry = 0; $result = array(); for ($i = count($dividend) - 1; $i >= 0; --$i) { $temp = 0x4000000 * $carry + (!empty($dividend[$i]) ? $dividend[$i] : 0); $result[$i] = (int) ($temp / $divisor); $carry = (int) ($temp - $divisor * $result[$i]); } return array($result, $carry); } /** * Performs modular exponentiation. * * Here's an example: * * modPow($b, $c); * * echo $c->toString(); // outputs 10 * ?> * * * @param Math_BigInteger $e * @param Math_BigInteger $n * @return Math_BigInteger * @access public * @internal The most naive approach to modular exponentiation has very unreasonable requirements, and * and although the approach involving repeated squaring does vastly better, it, too, is impractical * for our purposes. The reason being that division - by far the most complicated and time-consuming * of the basic operations (eg. +,-,*,/) - occurs multiple times within it. * * Modular reductions resolve this issue. Although an individual modular reduction takes more time * then an individual division, when performed in succession (with the same modulo), they're a lot faster. * * The two most commonly used modular reductions are Barrett and Montgomery reduction. Montgomery reduction, * although faster, only works when the gcd of the modulo and of the base being used is 1. In RSA, when the * base is a power of two, the modulo - a product of two primes - is always going to have a gcd of 1 (because * the product of two odd numbers is odd), but what about when RSA isn't used? * * In contrast, Barrett reduction has no such constraint. As such, some bigint implementations perform a * Barrett reduction after every operation in the modpow function. Others perform Barrett reductions when the * modulo is even and Montgomery reductions when the modulo is odd. BigInteger.java's modPow method, however, * uses a trick involving the Chinese Remainder Theorem to factor the even modulo into two numbers - one odd and * the other, a power of two - and recombine them, later. This is the method that this modPow function uses. * {@link http://islab.oregonstate.edu/papers/j34monex.pdf Montgomery Reduction with Even Modulus} elaborates. */ function modPow($e, $n) { $n = $this->bitmask !== false && $this->bitmask->compare($n) < 0 ? $this->bitmask : $n->abs(); if ($e->compare(new Math_BigInteger()) < 0) { $e = $e->abs(); $temp = $this->modInverse($n); if ($temp === false) { return false; } return $this->_normalize($temp->modPow($e, $n)); } switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = new Math_BigInteger(); $temp->value = gmp_powm($this->value, $e->value, $n->value); return $this->_normalize($temp); case MATH_BIGINTEGER_MODE_BCMATH: $temp = new Math_BigInteger(); $temp->value = bcpowmod($this->value, $e->value, $n->value, 0); return $this->_normalize($temp); } if ( empty($e->value) ) { $temp = new Math_BigInteger(); $temp->value = array(1); return $this->_normalize($temp); } if ( $e->value == array(1) ) { list(, $temp) = $this->divide($n); return $this->_normalize($temp); } if ( $e->value == array(2) ) { $temp = new Math_BigInteger(); $temp->value = $this->_square($this->value); list(, $temp) = $temp->divide($n); return $this->_normalize($temp); } return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_BARRETT)); // is the modulo odd? if ( $n->value[0] & 1 ) { return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_MONTGOMERY)); } // if it's not, it's even // find the lowest set bit (eg. the max pow of 2 that divides $n) for ($i = 0; $i < count($n->value); ++$i) { if ( $n->value[$i] ) { $temp = decbin($n->value[$i]); $j = strlen($temp) - strrpos($temp, '1') - 1; $j+= 26 * $i; break; } } // at this point, 2^$j * $n/(2^$j) == $n $mod1 = $n->copy(); $mod1->_rshift($j); $mod2 = new Math_BigInteger(); $mod2->value = array(1); $mod2->_lshift($j); $part1 = ( $mod1->value != array(1) ) ? $this->_slidingWindow($e, $mod1, MATH_BIGINTEGER_MONTGOMERY) : new Math_BigInteger(); $part2 = $this->_slidingWindow($e, $mod2, MATH_BIGINTEGER_POWEROF2); $y1 = $mod2->modInverse($mod1); $y2 = $mod1->modInverse($mod2); $result = $part1->multiply($mod2); $result = $result->multiply($y1); $temp = $part2->multiply($mod1); $temp = $temp->multiply($y2); $result = $result->add($temp); list(, $result) = $result->divide($n); return $this->_normalize($result); } /** * Performs modular exponentiation. * * Alias for Math_BigInteger::modPow() * * @param Math_BigInteger $e * @param Math_BigInteger $n * @return Math_BigInteger * @access public */ function powMod($e, $n) { return $this->modPow($e, $n); } /** * Sliding Window k-ary Modular Exponentiation * * Based on {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=27 HAC 14.85} / * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=210 MPM 7.7}. In a departure from those algorithims, * however, this function performs a modular reduction after every multiplication and squaring operation. * As such, this function has the same preconditions that the reductions being used do. * * @param Math_BigInteger $e * @param Math_BigInteger $n * @param Integer $mode * @return Math_BigInteger * @access private */ function _slidingWindow($e, $n, $mode) { static $window_ranges = array(7, 25, 81, 241, 673, 1793); // from BigInteger.java's oddModPow function //static $window_ranges = array(0, 7, 36, 140, 450, 1303, 3529); // from MPM 7.3.1 $e_value = $e->value; $e_length = count($e_value) - 1; $e_bits = decbin($e_value[$e_length]); for ($i = $e_length - 1; $i >= 0; --$i) { $e_bits.= str_pad(decbin($e_value[$i]), 26, '0', STR_PAD_LEFT); } $e_length = strlen($e_bits); // calculate the appropriate window size. // $window_size == 3 if $window_ranges is between 25 and 81, for example. for ($i = 0, $window_size = 1; $e_length > $window_ranges[$i] && $i < count($window_ranges); ++$window_size, ++$i); $n_value = $n->value; // precompute $this^0 through $this^$window_size $powers = array(); $powers[1] = $this->_prepareReduce($this->value, $n_value, $mode); $powers[2] = $this->_squareReduce($powers[1], $n_value, $mode); // we do every other number since substr($e_bits, $i, $j+1) (see below) is supposed to end // in a 1. ie. it's supposed to be odd. $temp = 1 << ($window_size - 1); for ($i = 1; $i < $temp; ++$i) { $i2 = $i << 1; $powers[$i2 + 1] = $this->_multiplyReduce($powers[$i2 - 1], $powers[2], $n_value, $mode); } $result = array(1); $result = $this->_prepareReduce($result, $n_value, $mode); for ($i = 0; $i < $e_length; ) { if ( !$e_bits[$i] ) { $result = $this->_squareReduce($result, $n_value, $mode); ++$i; } else { for ($j = $window_size - 1; $j > 0; --$j) { if ( !empty($e_bits[$i + $j]) ) { break; } } for ($k = 0; $k <= $j; ++$k) {// eg. the length of substr($e_bits, $i, $j+1) $result = $this->_squareReduce($result, $n_value, $mode); } $result = $this->_multiplyReduce($result, $powers[bindec(substr($e_bits, $i, $j + 1))], $n_value, $mode); $i+=$j + 1; } } $temp = new Math_BigInteger(); $temp->value = $this->_reduce($result, $n_value, $mode); return $temp; } /** * Modular reduction * * For most $modes this will return the remainder. * * @see _slidingWindow() * @access private * @param Array $x * @param Array $n * @param Integer $mode * @return Array */ function _reduce($x, $n, $mode) { switch ($mode) { case MATH_BIGINTEGER_MONTGOMERY: return $this->_montgomery($x, $n); case MATH_BIGINTEGER_BARRETT: return $this->_barrett($x, $n); case MATH_BIGINTEGER_POWEROF2: $lhs = new Math_BigInteger(); $lhs->value = $x; $rhs = new Math_BigInteger(); $rhs->value = $n; return $x->_mod2($n); case MATH_BIGINTEGER_CLASSIC: $lhs = new Math_BigInteger(); $lhs->value = $x; $rhs = new Math_BigInteger(); $rhs->value = $n; list(, $temp) = $lhs->divide($rhs); return $temp->value; case MATH_BIGINTEGER_NONE: return $x; default: // an invalid $mode was provided } } /** * Modular reduction preperation * * @see _slidingWindow() * @access private * @param Array $x * @param Array $n * @param Integer $mode * @return Array */ function _prepareReduce($x, $n, $mode) { if ($mode == MATH_BIGINTEGER_MONTGOMERY) { return $this->_prepMontgomery($x, $n); } return $this->_reduce($x, $n, $mode); } /** * Modular multiply * * @see _slidingWindow() * @access private * @param Array $x * @param Array $y * @param Array $n * @param Integer $mode * @return Array */ function _multiplyReduce($x, $y, $n, $mode) { if ($mode == MATH_BIGINTEGER_MONTGOMERY) { return $this->_montgomeryMultiply($x, $y, $n); } $temp = $this->_multiply($x, false, $y, false); return $this->_reduce($temp[MATH_BIGINTEGER_VALUE], $n, $mode); } /** * Modular square * * @see _slidingWindow() * @access private * @param Array $x * @param Array $n * @param Integer $mode * @return Array */ function _squareReduce($x, $n, $mode) { if ($mode == MATH_BIGINTEGER_MONTGOMERY) { return $this->_montgomeryMultiply($x, $x, $n); } return $this->_reduce($this->_square($x), $n, $mode); } /** * Modulos for Powers of Two * * Calculates $x%$n, where $n = 2**$e, for some $e. Since this is basically the same as doing $x & ($n-1), * we'll just use this function as a wrapper for doing that. * * @see _slidingWindow() * @access private * @param Math_BigInteger * @return Math_BigInteger */ function _mod2($n) { $temp = new Math_BigInteger(); $temp->value = array(1); return $this->bitwise_and($n->subtract($temp)); } /** * Barrett Modular Reduction * * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=14 HAC 14.3.3} / * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=165 MPM 6.2.5} for more information. Modified slightly, * so as not to require negative numbers (initially, this script didn't support negative numbers). * * Employs "folding", as described at * {@link http://www.cosic.esat.kuleuven.be/publications/thesis-149.pdf#page=66 thesis-149.pdf#page=66}. To quote from * it, "the idea [behind folding] is to find a value x' such that x (mod m) = x' (mod m), with x' being smaller than x." * * Unfortunately, the "Barrett Reduction with Folding" algorithm described in thesis-149.pdf is not, as written, all that * usable on account of (1) its not using reasonable radix points as discussed in * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=162 MPM 6.2.2} and (2) the fact that, even with reasonable * radix points, it only works when there are an even number of digits in the denominator. The reason for (2) is that * (x >> 1) + (x >> 1) != x / 2 + x / 2. If x is even, they're the same, but if x is odd, they're not. See the in-line * comments for details. * * @see _slidingWindow() * @access private * @param Array $n * @param Array $m * @return Array */ function _barrett($n, $m) { static $cache = array( MATH_BIGINTEGER_VARIABLE => array(), MATH_BIGINTEGER_DATA => array() ); $m_length = count($m); // if ($this->_compare($n, $this->_square($m)) >= 0) { if (count($n) > 2 * $m_length) { $lhs = new Math_BigInteger(); $rhs = new Math_BigInteger(); $lhs->value = $n; $rhs->value = $m; list(, $temp) = $lhs->divide($rhs); return $temp->value; } // if (m.length >> 1) + 2 <= m.length then m is too small and n can't be reduced if ($m_length < 5) { return $this->_regularBarrett($n, $m); } // n = 2 * m.length if ( ($key = array_search($m, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { $key = count($cache[MATH_BIGINTEGER_VARIABLE]); $cache[MATH_BIGINTEGER_VARIABLE][] = $m; $lhs = new Math_BigInteger(); $lhs_value = &$lhs->value; $lhs_value = $this->_array_repeat(0, $m_length + ($m_length >> 1)); $lhs_value[] = 1; $rhs = new Math_BigInteger(); $rhs->value = $m; list($u, $m1) = $lhs->divide($rhs); $u = $u->value; $m1 = $m1->value; $cache[MATH_BIGINTEGER_DATA][] = array( 'u' => $u, // m.length >> 1 (technically (m.length >> 1) + 1) 'm1'=> $m1 // m.length ); } else { extract($cache[MATH_BIGINTEGER_DATA][$key]); } $cutoff = $m_length + ($m_length >> 1); $lsd = array_slice($n, 0, $cutoff); // m.length + (m.length >> 1) $msd = array_slice($n, $cutoff); // m.length >> 1 $lsd = $this->_trim($lsd); $temp = $this->_multiply($msd, false, $m1, false); $n = $this->_add($lsd, false, $temp[MATH_BIGINTEGER_VALUE], false); // m.length + (m.length >> 1) + 1 if ($m_length & 1) { return $this->_regularBarrett($n[MATH_BIGINTEGER_VALUE], $m); } // (m.length + (m.length >> 1) + 1) - (m.length - 1) == (m.length >> 1) + 2 $temp = array_slice($n[MATH_BIGINTEGER_VALUE], $m_length - 1); // if even: ((m.length >> 1) + 2) + (m.length >> 1) == m.length + 2 // if odd: ((m.length >> 1) + 2) + (m.length >> 1) == (m.length - 1) + 2 == m.length + 1 $temp = $this->_multiply($temp, false, $u, false); // if even: (m.length + 2) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + 1 // if odd: (m.length + 1) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) $temp = array_slice($temp[MATH_BIGINTEGER_VALUE], ($m_length >> 1) + 1); // if even: (m.length - (m.length >> 1) + 1) + m.length = 2 * m.length - (m.length >> 1) + 1 // if odd: (m.length - (m.length >> 1)) + m.length = 2 * m.length - (m.length >> 1) $temp = $this->_multiply($temp, false, $m, false); // at this point, if m had an odd number of digits, we'd be subtracting a 2 * m.length - (m.length >> 1) digit // number from a m.length + (m.length >> 1) + 1 digit number. ie. there'd be an extra digit and the while loop // following this comment would loop a lot (hence our calling _regularBarrett() in that situation). $result = $this->_subtract($n[MATH_BIGINTEGER_VALUE], false, $temp[MATH_BIGINTEGER_VALUE], false); while ($this->_compare($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $m, false) >= 0) { $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $m, false); } return $result[MATH_BIGINTEGER_VALUE]; } /** * (Regular) Barrett Modular Reduction * * For numbers with more than four digits Math_BigInteger::_barrett() is faster. The difference between that and this * is that this function does not fold the denominator into a smaller form. * * @see _slidingWindow() * @access private * @param Array $x * @param Array $n * @return Array */ function _regularBarrett($x, $n) { static $cache = array( MATH_BIGINTEGER_VARIABLE => array(), MATH_BIGINTEGER_DATA => array() ); $n_length = count($n); if (count($x) > 2 * $n_length) { $lhs = new Math_BigInteger(); $rhs = new Math_BigInteger(); $lhs->value = $x; $rhs->value = $n; list(, $temp) = $lhs->divide($rhs); return $temp->value; } if ( ($key = array_search($n, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { $key = count($cache[MATH_BIGINTEGER_VARIABLE]); $cache[MATH_BIGINTEGER_VARIABLE][] = $n; $lhs = new Math_BigInteger(); $lhs_value = &$lhs->value; $lhs_value = $this->_array_repeat(0, 2 * $n_length); $lhs_value[] = 1; $rhs = new Math_BigInteger(); $rhs->value = $n; list($temp, ) = $lhs->divide($rhs); // m.length $cache[MATH_BIGINTEGER_DATA][] = $temp->value; } // 2 * m.length - (m.length - 1) = m.length + 1 $temp = array_slice($x, $n_length - 1); // (m.length + 1) + m.length = 2 * m.length + 1 $temp = $this->_multiply($temp, false, $cache[MATH_BIGINTEGER_DATA][$key], false); // (2 * m.length + 1) - (m.length - 1) = m.length + 2 $temp = array_slice($temp[MATH_BIGINTEGER_VALUE], $n_length + 1); // m.length + 1 $result = array_slice($x, 0, $n_length + 1); // m.length + 1 $temp = $this->_multiplyLower($temp, false, $n, false, $n_length + 1); // $temp == array_slice($temp->_multiply($temp, false, $n, false)->value, 0, $n_length + 1) if ($this->_compare($result, false, $temp[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_SIGN]) < 0) { $corrector_value = $this->_array_repeat(0, $n_length + 1); $corrector_value[] = 1; $result = $this->_add($result, false, $corrector, false); $result = $result[MATH_BIGINTEGER_VALUE]; } // at this point, we're subtracting a number with m.length + 1 digits from another number with m.length + 1 digits $result = $this->_subtract($result, false, $temp[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_SIGN]); while ($this->_compare($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $n, false) > 0) { $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $n, false); } return $result[MATH_BIGINTEGER_VALUE]; } /** * Performs long multiplication up to $stop digits * * If you're going to be doing array_slice($product->value, 0, $stop), some cycles can be saved. * * @see _regularBarrett() * @param Array $x_value * @param Boolean $x_negative * @param Array $y_value * @param Boolean $y_negative * @return Array * @access private */ function _multiplyLower($x_value, $x_negative, $y_value, $y_negative, $stop) { $x_length = count($x_value); $y_length = count($y_value); if ( !$x_length || !$y_length ) { // a 0 is being multiplied return array( MATH_BIGINTEGER_VALUE => array(), MATH_BIGINTEGER_SIGN => false ); } if ( $x_length < $y_length ) { $temp = $x_value; $x_value = $y_value; $y_value = $temp; $x_length = count($x_value); $y_length = count($y_value); } $product_value = $this->_array_repeat(0, $x_length + $y_length); // the following for loop could be removed if the for loop following it // (the one with nested for loops) initially set $i to 0, but // doing so would also make the result in one set of unnecessary adds, // since on the outermost loops first pass, $product->value[$k] is going // to always be 0 $carry = 0; for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0, $k = $i $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0 $carry = (int) ($temp / 0x4000000); $product_value[$j] = (int) ($temp - 0x4000000 * $carry); } if ($j < $stop) { $product_value[$j] = $carry; } // the above for loop is what the previous comment was talking about. the // following for loop is the "one with nested for loops" for ($i = 1; $i < $y_length; ++$i) { $carry = 0; for ($j = 0, $k = $i; $j < $x_length && $k < $stop; ++$j, ++$k) { $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; $carry = (int) ($temp / 0x4000000); $product_value[$k] = (int) ($temp - 0x4000000 * $carry); } if ($k < $stop) { $product_value[$k] = $carry; } } return array( MATH_BIGINTEGER_VALUE => $this->_trim($product_value), MATH_BIGINTEGER_SIGN => $x_negative != $y_negative ); } /** * Montgomery Modular Reduction * * ($x->_prepMontgomery($n))->_montgomery($n) yields $x % $n. * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=170 MPM 6.3} provides insights on how this can be * improved upon (basically, by using the comba method). gcd($n, 2) must be equal to one for this function * to work correctly. * * @see _prepMontgomery() * @see _slidingWindow() * @access private * @param Array $x * @param Array $n * @return Array */ function _montgomery($x, $n) { static $cache = array( MATH_BIGINTEGER_VARIABLE => array(), MATH_BIGINTEGER_DATA => array() ); if ( ($key = array_search($n, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { $key = count($cache[MATH_BIGINTEGER_VARIABLE]); $cache[MATH_BIGINTEGER_VARIABLE][] = $x; $cache[MATH_BIGINTEGER_DATA][] = $this->_modInverse67108864($n); } $k = count($n); $result = array(MATH_BIGINTEGER_VALUE => $x); for ($i = 0; $i < $k; ++$i) { $temp = $result[MATH_BIGINTEGER_VALUE][$i] * $cache[MATH_BIGINTEGER_DATA][$key]; $temp = (int) ($temp - 0x4000000 * ((int) ($temp / 0x4000000))); $temp = $this->_regularMultiply(array($temp), $n); $temp = array_merge($this->_array_repeat(0, $i), $temp); $result = $this->_add($result[MATH_BIGINTEGER_VALUE], false, $temp, false); } $result[MATH_BIGINTEGER_VALUE] = array_slice($result[MATH_BIGINTEGER_VALUE], $k); if ($this->_compare($result, false, $n, false) >= 0) { $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], false, $n, false); } return $result[MATH_BIGINTEGER_VALUE]; } /** * Montgomery Multiply * * Interleaves the montgomery reduction and long multiplication algorithms together as described in * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=13 HAC 14.36} * * @see _prepMontgomery() * @see _montgomery() * @access private * @param Array $x * @param Array $y * @param Array $m * @return Array */ function _montgomeryMultiply($x, $y, $m) { $temp = $this->_multiply($x, false, $y, false); return $this->_montgomery($temp[MATH_BIGINTEGER_VALUE], $m); static $cache = array( MATH_BIGINTEGER_VARIABLE => array(), MATH_BIGINTEGER_DATA => array() ); if ( ($key = array_search($m, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { $key = count($cache[MATH_BIGINTEGER_VARIABLE]); $cache[MATH_BIGINTEGER_VARIABLE][] = $m; $cache[MATH_BIGINTEGER_DATA][] = $this->_modInverse67108864($m); } $n = max(count($x), count($y), count($m)); $x = array_pad($x, $n, 0); $y = array_pad($y, $n, 0); $m = array_pad($m, $n, 0); $a = array(MATH_BIGINTEGER_VALUE => $this->_array_repeat(0, $n + 1)); for ($i = 0; $i < $n; ++$i) { $temp = $a[MATH_BIGINTEGER_VALUE][0] + $x[$i] * $y[0]; $temp = (int) ($temp - 0x4000000 * ((int) ($temp / 0x4000000))); $temp = $temp * $cache[MATH_BIGINTEGER_DATA][$key]; $temp = (int) ($temp - 0x4000000 * ((int) ($temp / 0x4000000))); $temp = $this->_add($this->_regularMultiply(array($x[$i]), $y), false, $this->_regularMultiply(array($temp), $m), false); $a = $this->_add($a[MATH_BIGINTEGER_VALUE], false, $temp[MATH_BIGINTEGER_VALUE], false); $a[MATH_BIGINTEGER_VALUE] = array_slice($a[MATH_BIGINTEGER_VALUE], 1); } if ($this->_compare($a[MATH_BIGINTEGER_VALUE], false, $m, false) >= 0) { $a = $this->_subtract($a[MATH_BIGINTEGER_VALUE], false, $m, false); } return $a[MATH_BIGINTEGER_VALUE]; } /** * Prepare a number for use in Montgomery Modular Reductions * * @see _montgomery() * @see _slidingWindow() * @access private * @param Array $x * @param Array $n * @return Array */ function _prepMontgomery($x, $n) { $lhs = new Math_BigInteger(); $lhs->value = array_merge($this->_array_repeat(0, count($n)), $x); $rhs = new Math_BigInteger(); $rhs->value = $n; list(, $temp) = $lhs->divide($rhs); return $temp->value; } /** * Modular Inverse of a number mod 2**26 (eg. 67108864) * * Based off of the bnpInvDigit function implemented and justified in the following URL: * * {@link http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js} * * The following URL provides more info: * * {@link http://groups.google.com/group/sci.crypt/msg/7a137205c1be7d85} * * As for why we do all the bitmasking... strange things can happen when converting from floats to ints. For * instance, on some computers, var_dump((int) -4294967297) yields int(-1) and on others, it yields * int(-2147483648). To avoid problems stemming from this, we use bitmasks to guarantee that ints aren't * auto-converted to floats. The outermost bitmask is present because without it, there's no guarantee that * the "residue" returned would be the so-called "common residue". We use fmod, in the last step, because the * maximum possible $x is 26 bits and the maximum $result is 16 bits. Thus, we have to be able to handle up to * 40 bits, which only 64-bit floating points will support. * * Thanks to Pedro Gimeno Fortea for input! * * @see _montgomery() * @access private * @param Array $x * @return Integer */ function _modInverse67108864($x) // 2**26 == 67108864 { $x = -$x[0]; $result = $x & 0x3; // x**-1 mod 2**2 $result = ($result * (2 - $x * $result)) & 0xF; // x**-1 mod 2**4 $result = ($result * (2 - ($x & 0xFF) * $result)) & 0xFF; // x**-1 mod 2**8 $result = ($result * ((2 - ($x & 0xFFFF) * $result) & 0xFFFF)) & 0xFFFF; // x**-1 mod 2**16 $result = fmod($result * (2 - fmod($x * $result, 0x4000000)), 0x4000000); // x**-1 mod 2**26 return $result & 0x3FFFFFF; } /** * Calculates modular inverses. * * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. * * Here's an example: * * modInverse($b); * echo $c->toString(); // outputs 4 * * echo "\r\n"; * * $d = $a->multiply($c); * list(, $d) = $d->divide($b); * echo $d; // outputs 1 (as per the definition of modular inverse) * ?> * * * @param Math_BigInteger $n * @return mixed false, if no modular inverse exists, Math_BigInteger, otherwise. * @access public * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=21 HAC 14.64} for more information. */ function modInverse($n) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = new Math_BigInteger(); $temp->value = gmp_invert($this->value, $n->value); return ( $temp->value === false ) ? false : $this->_normalize($temp); } static $zero, $one; if (!isset($zero)) { $zero = new Math_BigInteger(); $one = new Math_BigInteger(1); } // $x mod $n == $x mod -$n. $n = $n->abs(); if ($this->compare($zero) < 0) { $temp = $this->abs(); $temp = $temp->modInverse($n); return $negated === false ? false : $this->_normalize($n->subtract($temp)); } extract($this->extendedGCD($n)); if (!$gcd->equals($one)) { return false; } $x = $x->compare($zero) < 0 ? $x->add($n) : $x; return $this->compare($zero) < 0 ? $this->_normalize($n->subtract($x)) : $this->_normalize($x); } /** * Calculates the greatest common divisor and B�zout's identity. * * Say you have 693 and 609. The GCD is 21. B�zout's identity states that there exist integers x and y such that * 693*x + 609*y == 21. In point of fact, there are actually an infinite number of x and y combinations and which * combination is returned is dependant upon which mode is in use. See * {@link http://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity B�zout's identity - Wikipedia} for more information. * * Here's an example: * * extendedGCD($b)); * * echo $gcd->toString() . "\r\n"; // outputs 21 * echo $a->toString() * $x->toString() + $b->toString() * $y->toString(); // outputs 21 * ?> * * * @param Math_BigInteger $n * @return Math_BigInteger * @access public * @internal Calculates the GCD using the binary xGCD algorithim described in * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=19 HAC 14.61}. As the text above 14.61 notes, * the more traditional algorithim requires "relatively costly multiple-precision divisions". */ function extendedGCD($n) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: extract(gmp_gcdext($this->value, $n->value)); return array( 'gcd' => $this->_normalize(new Math_BigInteger($g)), 'x' => $this->_normalize(new Math_BigInteger($s)), 'y' => $this->_normalize(new Math_BigInteger($t)) ); case MATH_BIGINTEGER_MODE_BCMATH: // it might be faster to use the binary xGCD algorithim here, as well, but (1) that algorithim works // best when the base is a power of 2 and (2) i don't think it'd make much difference, anyway. as is, // the basic extended euclidean algorithim is what we're using. $u = $this->value; $v = $n->value; $a = '1'; $b = '0'; $c = '0'; $d = '1'; while (bccomp($v, '0', 0) != 0) { $q = bcdiv($u, $v, 0); $temp = $u; $u = $v; $v = bcsub($temp, bcmul($v, $q, 0), 0); $temp = $a; $a = $c; $c = bcsub($temp, bcmul($a, $q, 0), 0); $temp = $b; $b = $d; $d = bcsub($temp, bcmul($b, $q, 0), 0); } return array( 'gcd' => $this->_normalize(new Math_BigInteger($u)), 'x' => $this->_normalize(new Math_BigInteger($a)), 'y' => $this->_normalize(new Math_BigInteger($b)) ); } $y = $n->copy(); $x = $this->copy(); $g = new Math_BigInteger(); $g->value = array(1); while ( !(($x->value[0] & 1)|| ($y->value[0] & 1)) ) { $x->_rshift(1); $y->_rshift(1); $g->_lshift(1); } $u = $x->copy(); $v = $y->copy(); $a = new Math_BigInteger(); $b = new Math_BigInteger(); $c = new Math_BigInteger(); $d = new Math_BigInteger(); $a->value = $d->value = $g->value = array(1); $b->value = $c->value = array(); while ( !empty($u->value) ) { while ( !($u->value[0] & 1) ) { $u->_rshift(1); if ( (!empty($a->value) && ($a->value[0] & 1)) || (!empty($b->value) && ($b->value[0] & 1)) ) { $a = $a->add($y); $b = $b->subtract($x); } $a->_rshift(1); $b->_rshift(1); } while ( !($v->value[0] & 1) ) { $v->_rshift(1); if ( (!empty($d->value) && ($d->value[0] & 1)) || (!empty($c->value) && ($c->value[0] & 1)) ) { $c = $c->add($y); $d = $d->subtract($x); } $c->_rshift(1); $d->_rshift(1); } if ($u->compare($v) >= 0) { $u = $u->subtract($v); $a = $a->subtract($c); $b = $b->subtract($d); } else { $v = $v->subtract($u); $c = $c->subtract($a); $d = $d->subtract($b); } } return array( 'gcd' => $this->_normalize($g->multiply($v)), 'x' => $this->_normalize($c), 'y' => $this->_normalize($d) ); } /** * Calculates the greatest common divisor * * Say you have 693 and 609. The GCD is 21. * * Here's an example: * * extendedGCD($b); * * echo $gcd->toString() . "\r\n"; // outputs 21 * ?> * * * @param Math_BigInteger $n * @return Math_BigInteger * @access public */ function gcd($n) { extract($this->extendedGCD($n)); return $gcd; } /** * Absolute value. * * @return Math_BigInteger * @access public */ function abs() { $temp = new Math_BigInteger(); switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp->value = gmp_abs($this->value); break; case MATH_BIGINTEGER_MODE_BCMATH: $temp->value = (bccomp($this->value, '0', 0) < 0) ? substr($this->value, 1) : $this->value; break; default: $temp->value = $this->value; } return $temp; } /** * Compares two numbers. * * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this is * demonstrated thusly: * * $x > $y: $x->compare($y) > 0 * $x < $y: $x->compare($y) < 0 * $x == $y: $x->compare($y) == 0 * * Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y). * * @param Math_BigInteger $x * @return Integer < 0 if $this is less than $x; > 0 if $this is greater than $x, and 0 if they are equal. * @access public * @see equals() * @internal Could return $this->subtract($x), but that's not as fast as what we do do. */ function compare($y) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: return gmp_cmp($this->value, $y->value); case MATH_BIGINTEGER_MODE_BCMATH: return bccomp($this->value, $y->value, 0); } return $this->_compare($this->value, $this->is_negative, $y->value, $y->is_negative); } /** * Compares two numbers. * * @param Array $x_value * @param Boolean $x_negative * @param Array $y_value * @param Boolean $y_negative * @return Integer * @see compare() * @access private */ function _compare($x_value, $x_negative, $y_value, $y_negative) { if ( $x_negative != $y_negative ) { return ( !$x_negative && $y_negative ) ? 1 : -1; } $result = $x_negative ? -1 : 1; if ( count($x_value) != count($y_value) ) { return ( count($x_value) > count($y_value) ) ? $result : -$result; } $size = max(count($x_value), count($y_value)); $x_value = array_pad($x_value, $size, 0); $y_value = array_pad($y_value, $size, 0); for ($i = count($x_value) - 1; $i >= 0; --$i) { if ($x_value[$i] != $y_value[$i]) { return ( $x_value[$i] > $y_value[$i] ) ? $result : -$result; } } return 0; } /** * Tests the equality of two numbers. * * If you need to see if one number is greater than or less than another number, use Math_BigInteger::compare() * * @param Math_BigInteger $x * @return Boolean * @access public * @see compare() */ function equals($x) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: return gmp_cmp($this->value, $x->value) == 0; default: return $this->value === $x->value && $this->is_negative == $x->is_negative; } } /** * Set Precision * * Some bitwise operations give different results depending on the precision being used. Examples include left * shift, not, and rotates. * * @param Math_BigInteger $x * @access public * @return Math_BigInteger */ function setPrecision($bits) { $this->precision = $bits; if ( MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH ) { $this->bitmask = new Math_BigInteger(chr((1 << ($bits & 0x7)) - 1) . str_repeat(chr(0xFF), $bits >> 3), 256); } else { $this->bitmask = new Math_BigInteger(bcpow('2', $bits, 0)); } $temp = $this->_normalize($this); $this->value = $temp->value; } /** * Logical And * * @param Math_BigInteger $x * @access public * @internal Implemented per a request by Lluis Pamies i Juarez * @return Math_BigInteger */ function bitwise_and($x) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = new Math_BigInteger(); $temp->value = gmp_and($this->value, $x->value); return $this->_normalize($temp); case MATH_BIGINTEGER_MODE_BCMATH: $left = $this->toBytes(); $right = $x->toBytes(); $length = max(strlen($left), strlen($right)); $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); return $this->_normalize(new Math_BigInteger($left & $right, 256)); } $result = $this->copy(); $length = min(count($x->value), count($this->value)); $result->value = array_slice($result->value, 0, $length); for ($i = 0; $i < $length; ++$i) { $result->value[$i] = $result->value[$i] & $x->value[$i]; } return $this->_normalize($result); } /** * Logical Or * * @param Math_BigInteger $x * @access public * @internal Implemented per a request by Lluis Pamies i Juarez * @return Math_BigInteger */ function bitwise_or($x) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = new Math_BigInteger(); $temp->value = gmp_or($this->value, $x->value); return $this->_normalize($temp); case MATH_BIGINTEGER_MODE_BCMATH: $left = $this->toBytes(); $right = $x->toBytes(); $length = max(strlen($left), strlen($right)); $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); return $this->_normalize(new Math_BigInteger($left | $right, 256)); } $length = max(count($this->value), count($x->value)); $result = $this->copy(); $result->value = array_pad($result->value, 0, $length); $x->value = array_pad($x->value, 0, $length); for ($i = 0; $i < $length; ++$i) { $result->value[$i] = $this->value[$i] | $x->value[$i]; } return $this->_normalize($result); } /** * Logical Exclusive-Or * * @param Math_BigInteger $x * @access public * @internal Implemented per a request by Lluis Pamies i Juarez * @return Math_BigInteger */ function bitwise_xor($x) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = new Math_BigInteger(); $temp->value = gmp_xor($this->value, $x->value); return $this->_normalize($temp); case MATH_BIGINTEGER_MODE_BCMATH: $left = $this->toBytes(); $right = $x->toBytes(); $length = max(strlen($left), strlen($right)); $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); return $this->_normalize(new Math_BigInteger($left ^ $right, 256)); } $length = max(count($this->value), count($x->value)); $result = $this->copy(); $result->value = array_pad($result->value, 0, $length); $x->value = array_pad($x->value, 0, $length); for ($i = 0; $i < $length; ++$i) { $result->value[$i] = $this->value[$i] ^ $x->value[$i]; } return $this->_normalize($result); } /** * Logical Not * * @access public * @internal Implemented per a request by Lluis Pamies i Juarez * @return Math_BigInteger */ function bitwise_not() { // calculuate "not" without regard to $this->precision // (will always result in a smaller number. ie. ~1 isn't 1111 1110 - it's 0) $temp = $this->toBytes(); $pre_msb = decbin(ord($temp[0])); $temp = ~$temp; $msb = decbin(ord($temp[0])); if (strlen($msb) == 8) { $msb = substr($msb, strpos($msb, '0')); } $temp[0] = chr(bindec($msb)); // see if we need to add extra leading 1's $current_bits = strlen($pre_msb) + 8 * strlen($temp) - 8; $new_bits = $this->precision - $current_bits; if ($new_bits <= 0) { return $this->_normalize(new Math_BigInteger($temp, 256)); } // generate as many leading 1's as we need to. $leading_ones = chr((1 << ($new_bits & 0x7)) - 1) . str_repeat(chr(0xFF), $new_bits >> 3); $this->_base256_lshift($leading_ones, $current_bits); $temp = str_pad($temp, ceil($this->bits / 8), chr(0), STR_PAD_LEFT); return $this->_normalize(new Math_BigInteger($leading_ones | $temp, 256)); } /** * Logical Right Shift * * Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift. * * @param Integer $shift * @return Math_BigInteger * @access public * @internal The only version that yields any speed increases is the internal version. */ function bitwise_rightShift($shift) { $temp = new Math_BigInteger(); switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: static $two; if (!isset($two)) { $two = gmp_init('2'); } $temp->value = gmp_div_q($this->value, gmp_pow($two, $shift)); break; case MATH_BIGINTEGER_MODE_BCMATH: $temp->value = bcdiv($this->value, bcpow('2', $shift, 0), 0); break; default: // could just replace _lshift with this, but then all _lshift() calls would need to be rewritten // and I don't want to do that... $temp->value = $this->value; $temp->_rshift($shift); } return $this->_normalize($temp); } /** * Logical Left Shift * * Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift. * * @param Integer $shift * @return Math_BigInteger * @access public * @internal The only version that yields any speed increases is the internal version. */ function bitwise_leftShift($shift) { $temp = new Math_BigInteger(); switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: static $two; if (!isset($two)) { $two = gmp_init('2'); } $temp->value = gmp_mul($this->value, gmp_pow($two, $shift)); break; case MATH_BIGINTEGER_MODE_BCMATH: $temp->value = bcmul($this->value, bcpow('2', $shift, 0), 0); break; default: // could just replace _rshift with this, but then all _lshift() calls would need to be rewritten // and I don't want to do that... $temp->value = $this->value; $temp->_lshift($shift); } return $this->_normalize($temp); } /** * Logical Left Rotate * * Instead of the top x bits being dropped they're appended to the shifted bit string. * * @param Integer $shift * @return Math_BigInteger * @access public */ function bitwise_leftRotate($shift) { $bits = $this->toBytes(); if ($this->precision > 0) { $precision = $this->precision; if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH ) { $mask = $this->bitmask->subtract(new Math_BigInteger(1)); $mask = $mask->toBytes(); } else { $mask = $this->bitmask->toBytes(); } } else { $temp = ord($bits[0]); for ($i = 0; $temp >> $i; ++$i); $precision = 8 * strlen($bits) - 8 + $i; $mask = chr((1 << ($precision & 0x7)) - 1) . str_repeat(chr(0xFF), $precision >> 3); } if ($shift < 0) { $shift+= $precision; } $shift%= $precision; if (!$shift) { return $this->copy(); } $left = $this->bitwise_leftShift($shift); $left = $left->bitwise_and(new Math_BigInteger($mask, 256)); $right = $this->bitwise_rightShift($precision - $shift); $result = MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH ? $left->bitwise_or($right) : $left->add($right); return $this->_normalize($result); } /** * Logical Right Rotate * * Instead of the bottom x bits being dropped they're prepended to the shifted bit string. * * @param Integer $shift * @return Math_BigInteger * @access public */ function bitwise_rightRotate($shift) { return $this->bitwise_leftRotate(-$shift); } /** * Set random number generator function * * $generator should be the name of a random generating function whose first parameter is the minimum * value and whose second parameter is the maximum value. If this function needs to be seeded, it should * be seeded prior to calling Math_BigInteger::random() or Math_BigInteger::randomPrime() * * If the random generating function is not explicitly set, it'll be assumed to be mt_rand(). * * @see random() * @see randomPrime() * @param optional String $generator * @access public */ function setRandomGenerator($generator) { $this->generator = $generator; } /** * Generate a random number * * @param optional Integer $min * @param optional Integer $max * @return Math_BigInteger * @access public */ function random($min = false, $max = false) { if ($min === false) { $min = new Math_BigInteger(0); } if ($max === false) { $max = new Math_BigInteger(0x7FFFFFFF); } $compare = $max->compare($min); if (!$compare) { return $this->_normalize($min); } else if ($compare < 0) { // if $min is bigger then $max, swap $min and $max $temp = $max; $max = $min; $min = $temp; } $generator = $this->generator; $max = $max->subtract($min); $max = ltrim($max->toBytes(), chr(0)); $size = strlen($max) - 1; $random = ''; $bytes = $size & 1; for ($i = 0; $i < $bytes; ++$i) { $random.= chr($generator(0, 255)); } $blocks = $size >> 1; for ($i = 0; $i < $blocks; ++$i) { // mt_rand(-2147483648, 0x7FFFFFFF) always produces -2147483648 on some systems $random.= pack('n', $generator(0, 0xFFFF)); } $temp = new Math_BigInteger($random, 256); if ($temp->compare(new Math_BigInteger(substr($max, 1), 256)) > 0) { $random = chr($generator(0, ord($max[0]) - 1)) . $random; } else { $random = chr($generator(0, ord($max[0]) )) . $random; } $random = new Math_BigInteger($random, 256); return $this->_normalize($random->add($min)); } /** * Generate a random prime number. * * If there's not a prime within the given range, false will be returned. If more than $timeout seconds have elapsed, * give up and return false. * * @param optional Integer $min * @param optional Integer $max * @param optional Integer $timeout * @return Math_BigInteger * @access public * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=15 HAC 4.44}. */ function randomPrime($min = false, $max = false, $timeout = false) { $compare = $max->compare($min); if (!$compare) { return $min; } else if ($compare < 0) { // if $min is bigger then $max, swap $min and $max $temp = $max; $max = $min; $min = $temp; } // gmp_nextprime() requires PHP 5 >= 5.2.0 per . if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_GMP && function_exists('gmp_nextprime') ) { // we don't rely on Math_BigInteger::random()'s min / max when gmp_nextprime() is being used since this function // does its own checks on $max / $min when gmp_nextprime() is used. When gmp_nextprime() is not used, however, // the same $max / $min checks are not performed. if ($min === false) { $min = new Math_BigInteger(0); } if ($max === false) { $max = new Math_BigInteger(0x7FFFFFFF); } $x = $this->random($min, $max); $x->value = gmp_nextprime($x->value); if ($x->compare($max) <= 0) { return $x; } $x->value = gmp_nextprime($min->value); if ($x->compare($max) <= 0) { return $x; } return false; } static $one, $two; if (!isset($one)) { $one = new Math_BigInteger(1); $two = new Math_BigInteger(2); } $start = time(); $x = $this->random($min, $max); if ($x->equals($two)) { return $x; } $x->_make_odd(); if ($x->compare($max) > 0) { // if $x > $max then $max is even and if $min == $max then no prime number exists between the specified range if ($min->equals($max)) { return false; } $x = $min->copy(); $x->_make_odd(); } $initial_x = $x->copy(); while (true) { if ($timeout !== false && time() - $start > $timeout) { return false; } if ($x->isPrime()) { return $x; } $x = $x->add($two); if ($x->compare($max) > 0) { $x = $min->copy(); if ($x->equals($two)) { return $x; } $x->_make_odd(); } if ($x->equals($initial_x)) { return false; } } } /** * Make the current number odd * * If the current number is odd it'll be unchanged. If it's even, one will be added to it. * * @see randomPrime() * @access private */ function _make_odd() { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: gmp_setbit($this->value, 0); break; case MATH_BIGINTEGER_MODE_BCMATH: if ($this->value[strlen($this->value) - 1] % 2 == 0) { $this->value = bcadd($this->value, '1'); } break; default: $this->value[0] |= 1; } } /** * Checks a numer to see if it's prime * * Assuming the $t parameter is not set, this function has an error rate of 2**-80. The main motivation for the * $t parameter is distributability. Math_BigInteger::randomPrime() can be distributed accross multiple pageloads * on a website instead of just one. * * @param optional Integer $t * @return Boolean * @access public * @internal Uses the * {@link http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test Miller-Rabin primality test}. See * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=8 HAC 4.24}. */ function isPrime($t = false) { $length = strlen($this->toBytes()); if (!$t) { // see HAC 4.49 "Note (controlling the error probability)" if ($length >= 163) { $t = 2; } // floor(1300 / 8) else if ($length >= 106) { $t = 3; } // floor( 850 / 8) else if ($length >= 81 ) { $t = 4; } // floor( 650 / 8) else if ($length >= 68 ) { $t = 5; } // floor( 550 / 8) else if ($length >= 56 ) { $t = 6; } // floor( 450 / 8) else if ($length >= 50 ) { $t = 7; } // floor( 400 / 8) else if ($length >= 43 ) { $t = 8; } // floor( 350 / 8) else if ($length >= 37 ) { $t = 9; } // floor( 300 / 8) else if ($length >= 31 ) { $t = 12; } // floor( 250 / 8) else if ($length >= 25 ) { $t = 15; } // floor( 200 / 8) else if ($length >= 18 ) { $t = 18; } // floor( 150 / 8) else { $t = 27; } } // ie. gmp_testbit($this, 0) // ie. isEven() or !isOdd() switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: return gmp_prob_prime($this->value, $t) != 0; case MATH_BIGINTEGER_MODE_BCMATH: if ($this->value === '2') { return true; } if ($this->value[strlen($this->value) - 1] % 2 == 0) { return false; } break; default: if ($this->value == array(2)) { return true; } if (~$this->value[0] & 1) { return false; } } static $primes, $zero, $one, $two; if (!isset($primes)) { $primes = array( 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997 ); if ( MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL ) { for ($i = 0; $i < count($primes); ++$i) { $primes[$i] = new Math_BigInteger($primes[$i]); } } $zero = new Math_BigInteger(); $one = new Math_BigInteger(1); $two = new Math_BigInteger(2); } if ($this->equals($one)) { return false; } // see HAC 4.4.1 "Random search for probable primes" if ( MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL ) { foreach ($primes as $prime) { list(, $r) = $this->divide($prime); if ($r->equals($zero)) { return $this->equals($prime); } } } else { $value = $this->value; foreach ($primes as $prime) { list(, $r) = $this->_divide_digit($value, $prime); if (!$r) { return count($value) == 1 && $value[0] == $prime; } } } $n = $this->copy(); $n_1 = $n->subtract($one); $n_2 = $n->subtract($two); $r = $n_1->copy(); $r_value = $r->value; // ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s)); if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH ) { $s = 0; // if $n was 1, $r would be 0 and this would be an infinite loop, hence our $this->equals($one) check earlier while ($r->value[strlen($r->value) - 1] % 2 == 0) { $r->value = bcdiv($r->value, '2', 0); ++$s; } } else { for ($i = 0, $r_length = count($r_value); $i < $r_length; ++$i) { $temp = ~$r_value[$i] & 0xFFFFFF; for ($j = 1; ($temp >> $j) & 1; ++$j); if ($j != 25) { break; } } $s = 26 * $i + $j - 1; $r->_rshift($s); } for ($i = 0; $i < $t; ++$i) { $a = $this->random($two, $n_2); $y = $a->modPow($r, $n); if (!$y->equals($one) && !$y->equals($n_1)) { for ($j = 1; $j < $s && !$y->equals($n_1); ++$j) { $y = $y->modPow($two, $n); if ($y->equals($one)) { return false; } } if (!$y->equals($n_1)) { return false; } } } return true; } /** * Logical Left Shift * * Shifts BigInteger's by $shift bits. * * @param Integer $shift * @access private */ function _lshift($shift) { if ( $shift == 0 ) { return; } $num_digits = (int) ($shift / 26); $shift %= 26; $shift = 1 << $shift; $carry = 0; for ($i = 0; $i < count($this->value); ++$i) { $temp = $this->value[$i] * $shift + $carry; $carry = (int) ($temp / 0x4000000); $this->value[$i] = (int) ($temp - $carry * 0x4000000); } if ( $carry ) { $this->value[] = $carry; } while ($num_digits--) { array_unshift($this->value, 0); } } /** * Logical Right Shift * * Shifts BigInteger's by $shift bits. * * @param Integer $shift * @access private */ function _rshift($shift) { if ($shift == 0) { return; } $num_digits = (int) ($shift / 26); $shift %= 26; $carry_shift = 26 - $shift; $carry_mask = (1 << $shift) - 1; if ( $num_digits ) { $this->value = array_slice($this->value, $num_digits); } $carry = 0; for ($i = count($this->value) - 1; $i >= 0; --$i) { $temp = $this->value[$i] >> $shift | $carry; $carry = ($this->value[$i] & $carry_mask) << $carry_shift; $this->value[$i] = $temp; } $this->value = $this->_trim($this->value); } /** * Normalize * * Removes leading zeros and truncates (if necessary) to maintain the appropriate precision * * @param Math_BigInteger * @return Math_BigInteger * @see _trim() * @access private */ function _normalize($result) { $result->precision = $this->precision; $result->bitmask = $this->bitmask; switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: if (!empty($result->bitmask->value)) { $result->value = gmp_and($result->value, $result->bitmask->value); } return $result; case MATH_BIGINTEGER_MODE_BCMATH: if (!empty($result->bitmask->value)) { $result->value = bcmod($result->value, $result->bitmask->value); } return $result; } $value = &$result->value; if ( !count($value) ) { return $result; } $value = $this->_trim($value); if (!empty($result->bitmask->value)) { $length = min(count($value), count($this->bitmask->value)); $value = array_slice($value, 0, $length); for ($i = 0; $i < $length; ++$i) { $value[$i] = $value[$i] & $this->bitmask->value[$i]; } } return $result; } /** * Trim * * Removes leading zeros * * @return Math_BigInteger * @access private */ function _trim($value) { for ($i = count($value) - 1; $i >= 0; --$i) { if ( !empty($value[$i]) ) { break; } unset($value[$i]); } return $value; } /** * Array Repeat * * @param $input Array * @param $multiplier mixed * @return Array * @access private */ function _array_repeat($input, $multiplier) { return ($multiplier) ? array_fill(0, $multiplier, $input) : array(); } /** * Logical Left Shift * * Shifts binary strings $shift bits, essentially multiplying by 2**$shift. * * @param $x String * @param $shift Integer * @return String * @access private */ function _base256_lshift(&$x, $shift) { if ($shift == 0) { return; } $num_bytes = $shift >> 3; // eg. floor($shift/8) $shift &= 7; // eg. $shift % 8 $carry = 0; for ($i = strlen($x) - 1; $i >= 0; --$i) { $temp = ord($x[$i]) << $shift | $carry; $x[$i] = chr($temp); $carry = $temp >> 8; } $carry = ($carry != 0) ? chr($carry) : ''; $x = $carry . $x . str_repeat(chr(0), $num_bytes); } /** * Logical Right Shift * * Shifts binary strings $shift bits, essentially dividing by 2**$shift and returning the remainder. * * @param $x String * @param $shift Integer * @return String * @access private */ function _base256_rshift(&$x, $shift) { if ($shift == 0) { $x = ltrim($x, chr(0)); return ''; } $num_bytes = $shift >> 3; // eg. floor($shift/8) $shift &= 7; // eg. $shift % 8 $remainder = ''; if ($num_bytes) { $start = $num_bytes > strlen($x) ? -strlen($x) : -$num_bytes; $remainder = substr($x, $start); $x = substr($x, 0, -$num_bytes); } $carry = 0; $carry_shift = 8 - $shift; for ($i = 0; $i < strlen($x); ++$i) { $temp = (ord($x[$i]) >> $shift) | $carry; $carry = (ord($x[$i]) << $carry_shift) & 0xFF; $x[$i] = chr($temp); } $x = ltrim($x, chr(0)); $remainder = chr($carry >> $carry_shift) . $remainder; return ltrim($remainder, chr(0)); } // one quirk about how the following functions are implemented is that PHP defines N to be an unsigned long // at 32-bits, while java's longs are 64-bits. /** * Converts 32-bit integers to bytes. * * @param Integer $x * @return String * @access private */ function _int2bytes($x) { return ltrim(pack('N', $x), chr(0)); } /** * Converts bytes to 32-bit integers * * @param String $x * @return Integer * @access private */ function _bytes2int($x) { $temp = unpack('Nint', str_pad($x, 4, chr(0), STR_PAD_LEFT)); return $temp['int']; } }lib/hybridauth/Adapter/OAuth1.php000064400000044121147577535410012673 0ustar00consumerKey = $this->config->filter('keys')->get('id') ?: $this->config->filter('keys')->get('key'); $this->consumerSecret = $this->config->filter('keys')->get('secret'); if (!$this->consumerKey || !$this->consumerSecret) { throw new InvalidApplicationCredentialsException( 'Your application id is required in order to connect to ' . $this->providerId ); } if ($this->config->exists('tokens')) { $this->setAccessToken($this->config->get('tokens')); } $this->setCallback($this->config->get('callback')); $this->setApiEndpoints($this->config->get('endpoints')); } /** * {@inheritdoc} */ protected function initialize() { /** * Set up OAuth Signature and Consumer * * OAuth Core: All Token requests and Protected Resources requests MUST be signed * by the Consumer and verified by the Service Provider. * * The protocol defines three signature methods: HMAC-SHA1, RSA-SHA1, and PLAINTEXT.. * * The Consumer declares a signature method in the oauth_signature_method parameter.. * * http://oauth.net/core/1.0a/#signing_process */ $this->sha1Method = new OAuthSignatureMethodHMACSHA1(); $this->OAuthConsumer = new OAuthConsumer( $this->consumerKey, $this->consumerSecret ); if ($this->getStoredData('request_token')) { $this->consumerToken = new OAuthConsumer( $this->getStoredData('request_token'), $this->getStoredData('request_token_secret') ); } if ($this->getStoredData('access_token')) { $this->consumerToken = new OAuthConsumer( $this->getStoredData('access_token'), $this->getStoredData('access_token_secret') ); } } /** * {@inheritdoc} */ public function authenticate() { $this->logger->info(sprintf('%s::authenticate()', get_class($this))); if ($this->isConnected()) { return true; } try { if (!$this->getStoredData('request_token')) { // Start a new flow. $this->authenticateBegin(); } elseif (empty($_GET['oauth_token']) && empty($_GET['denied'])) { // A previous authentication was not finished, and this request is not finishing it. $this->authenticateBegin(); } else { // Finish a flow. $this->authenticateFinish(); } } catch (Exception $exception) { $this->clearStoredData(); throw $exception; } return null; } /** * {@inheritdoc} */ public function isConnected() { return (bool)$this->getStoredData('access_token'); } /** * Initiate the authorization protocol * * 1. Obtaining an Unauthorized Request Token * 2. Build Authorization URL for Authorization Request and redirect the user-agent to the * Authorization Server. */ protected function authenticateBegin() { $response = $this->requestAuthToken(); $this->validateAuthTokenRequest($response); $authUrl = $this->getAuthorizeUrl(); $this->logger->debug(sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this)), [$authUrl]); HttpClient\Util::redirect($authUrl); } /** * Finalize the authorization process * * @throws AuthorizationDeniedException * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException * @throws InvalidAccessTokenException * @throws InvalidOauthTokenException */ protected function authenticateFinish() { $this->logger->debug( sprintf('%s::authenticateFinish(), callback url:', get_class($this)), [HttpClient\Util::getCurrentUrl(true)] ); $denied = filter_input(INPUT_GET, 'denied'); $oauth_problem = filter_input(INPUT_GET, 'oauth_problem'); $oauth_token = filter_input(INPUT_GET, 'oauth_token'); $oauth_verifier = filter_input(INPUT_GET, 'oauth_verifier'); if ($denied) { throw new AuthorizationDeniedException( 'User denied access request. Provider returned a denied token: ' . htmlentities($denied) ); } if ($oauth_problem) { throw new InvalidOauthTokenException( 'Provider returned an error. oauth_problem: ' . htmlentities($oauth_problem) ); } if (!$oauth_token) { throw new InvalidOauthTokenException( 'Expecting a non-null oauth_token to continue the authorization flow.' ); } $response = $this->exchangeAuthTokenForAccessToken($oauth_token, $oauth_verifier); $this->validateAccessTokenExchange($response); $this->initialize(); } /** * Build Authorization URL for Authorization Request * * @param array $parameters * * @return string */ protected function getAuthorizeUrl($parameters = []) { $this->AuthorizeUrlParameters = !empty($parameters) ? $parameters : array_replace( (array)$this->AuthorizeUrlParameters, (array)$this->config->get('authorize_url_parameters') ); $this->AuthorizeUrlParameters['oauth_token'] = $this->getStoredData('request_token'); return $this->authorizeUrl . '?' . http_build_query($this->AuthorizeUrlParameters, '', '&'); } /** * Unauthorized Request Token * * OAuth Core: The Consumer obtains an unauthorized Request Token by asking the Service Provider * to issue a Token. The Request Token's sole purpose is to receive User approval and can only * be used to obtain an Access Token. * * http://oauth.net/core/1.0/#auth_step1 * 6.1.1. Consumer Obtains a Request Token * * @return string Raw Provider API response * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException */ protected function requestAuthToken() { /** * OAuth Core 1.0 Revision A: oauth_callback: An absolute URL to which the Service Provider will redirect * the User back when the Obtaining User Authorization step is completed. * * http://oauth.net/core/1.0a/#auth_step1 */ if ('1.0a' == $this->oauth1Version) { $this->requestTokenParameters['oauth_callback'] = $this->callback; } $response = $this->oauthRequest( $this->requestTokenUrl, $this->requestTokenMethod, $this->requestTokenParameters, $this->requestTokenHeaders ); return $response; } /** * Validate Unauthorized Request Token Response * * OAuth Core: The Service Provider verifies the signature and Consumer Key. If successful, * it generates a Request Token and Token Secret and returns them to the Consumer in the HTTP * response body. * * http://oauth.net/core/1.0/#auth_step1 * 6.1.2. Service Provider Issues an Unauthorized Request Token * * @param string $response * * @return \Hybridauth\Data\Collection * @throws InvalidOauthTokenException */ protected function validateAuthTokenRequest($response) { /** * The response contains the following parameters: * * - oauth_token The Request Token. * - oauth_token_secret The Token Secret. * - oauth_callback_confirmed MUST be present and set to true. * * http://oauth.net/core/1.0/#auth_step1 * 6.1.2. Service Provider Issues an Unauthorized Request Token * * Example of a successful response: * * HTTP/1.1 200 OK * Content-Type: text/html; charset=utf-8 * Cache-Control: no-store * Pragma: no-cache * * oauth_token=80359084-clg1DEtxQF3wstTcyUdHF3wsdHM&oauth_token_secret=OIF07hPmJB:P * 6qiHTi1znz6qiH3tTcyUdHnz6qiH3tTcyUdH3xW3wsDvV08e&example_parameter=example_value * * OAuthUtil::parse_parameters will attempt to decode the raw response into an array. */ $tokens = OAuthUtil::parse_parameters($response); $collection = new Data\Collection($tokens); if (!$collection->exists('oauth_token')) { throw new InvalidOauthTokenException( 'Provider returned no oauth_token: ' . htmlentities($response) ); } $this->consumerToken = new OAuthConsumer( $tokens['oauth_token'], $tokens['oauth_token_secret'] ); $this->storeData('request_token', $tokens['oauth_token']); $this->storeData('request_token_secret', $tokens['oauth_token_secret']); return $collection; } /** * Requests an Access Token * * OAuth Core: The Request Token and Token Secret MUST be exchanged for an Access Token and Token Secret. * * http://oauth.net/core/1.0a/#auth_step3 * 6.3.1. Consumer Requests an Access Token * * @param string $oauth_token * @param string $oauth_verifier * * @return string Raw Provider API response * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException */ protected function exchangeAuthTokenForAccessToken($oauth_token, $oauth_verifier = '') { $this->tokenExchangeParameters['oauth_token'] = $oauth_token; /** * OAuth Core 1.0 Revision A: oauth_verifier: The verification code received from the Service Provider * in the "Service Provider Directs the User Back to the Consumer" step. * * http://oauth.net/core/1.0a/#auth_step3 */ if ('1.0a' == $this->oauth1Version) { $this->tokenExchangeParameters['oauth_verifier'] = $oauth_verifier; } $response = $this->oauthRequest( $this->accessTokenUrl, $this->tokenExchangeMethod, $this->tokenExchangeParameters, $this->tokenExchangeHeaders ); return $response; } /** * Validate Access Token Response * * OAuth Core: If successful, the Service Provider generates an Access Token and Token Secret and returns * them in the HTTP response body. * * The Access Token and Token Secret are stored by the Consumer and used when signing Protected Resources requests. * * http://oauth.net/core/1.0a/#auth_step3 * 6.3.2. Service Provider Grants an Access Token * * @param string $response * * @return \Hybridauth\Data\Collection * @throws InvalidAccessTokenException */ protected function validateAccessTokenExchange($response) { /** * The response contains the following parameters: * * - oauth_token The Access Token. * - oauth_token_secret The Token Secret. * * http://oauth.net/core/1.0/#auth_step3 * 6.3.2. Service Provider Grants an Access Token * * Example of a successful response: * * HTTP/1.1 200 OK * Content-Type: text/html; charset=utf-8 * Cache-Control: no-store * Pragma: no-cache * * oauth_token=sHeLU7Far428zj8PzlWR75&oauth_token_secret=fXb30rzoG&oauth_callback_confirmed=true * * OAuthUtil::parse_parameters will attempt to decode the raw response into an array. */ $tokens = OAuthUtil::parse_parameters($response); $collection = new Data\Collection($tokens); if (!$collection->exists('oauth_token')) { throw new InvalidAccessTokenException( 'Provider returned no access_token: ' . htmlentities($response) ); } $this->consumerToken = new OAuthConsumer( $collection->get('oauth_token'), $collection->get('oauth_token_secret') ); $this->storeData('access_token', $collection->get('oauth_token')); $this->storeData('access_token_secret', $collection->get('oauth_token_secret')); $this->deleteStoredData('request_token'); $this->deleteStoredData('request_token_secret'); return $collection; } /** * Send a signed request to provider API * * Note: Since the specifics of error responses is beyond the scope of RFC6749 and OAuth specifications, * Hybridauth will consider any HTTP status code that is different than '200 OK' as an ERROR. * * @param string $url * @param string $method * @param array $parameters * @param array $headers * @param bool $multipart * * @return mixed * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException */ public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [], $multipart = false) { // refresh tokens if needed $this->maintainToken(); if (strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0) { $url = rtrim($this->apiBaseUrl, '/') . '/' . ltrim($url, '/'); } $parameters = array_replace($this->apiRequestParameters, (array)$parameters); $headers = array_replace($this->apiRequestHeaders, (array)$headers); $response = $this->oauthRequest($url, $method, $parameters, $headers, $multipart); $response = (new Data\Parser())->parse($response); return $response; } /** * Setup and Send a Signed Oauth Request * * This method uses OAuth Library. * * @param string $uri * @param string $method * @param array $parameters * @param array $headers * @param bool $multipart * * @return string Raw Provider API response * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException */ protected function oauthRequest($uri, $method = 'GET', $parameters = [], $headers = [], $multipart = false) { $signing_parameters = $parameters; if ($multipart) { $signing_parameters = []; } $request = OAuthRequest::from_consumer_and_token( $this->OAuthConsumer, $this->consumerToken, $method, $uri, $signing_parameters ); $request->sign_request( $this->sha1Method, $this->OAuthConsumer, $this->consumerToken ); $uri = $request->get_normalized_http_url(); $headers = array_replace($request->to_header(), (array)$headers); $response = $this->httpClient->request( $uri, $method, $parameters, $headers, $multipart ); $this->validateApiResponse('Signed API request to ' . $uri . ' has returned an error'); return $response; } } lib/hybridauth/Adapter/OAuth2.php000064400000054653147577535410012707 0ustar00clientId = $this->config->filter('keys')->get('id') ?: $this->config->filter('keys')->get('key'); $this->clientSecret = $this->config->filter('keys')->get('secret'); if (!$this->clientId || !$this->clientSecret) { throw new InvalidApplicationCredentialsException( 'Your application id is required in order to connect to ' . $this->providerId ); } $this->scope = $this->config->exists('scope') ? $this->config->get('scope') : $this->scope; if ($this->config->exists('tokens')) { $this->setAccessToken($this->config->get('tokens')); } if ($this->config->exists('supportRequestState')) { $this->supportRequestState = $this->config->get('supportRequestState'); } $this->setCallback($this->config->get('callback')); $this->setApiEndpoints($this->config->get('endpoints')); } /** * {@inheritdoc} */ protected function initialize() { $this->AuthorizeUrlParameters = [ 'response_type' => 'code', 'client_id' => $this->clientId, 'redirect_uri' => $this->callback, 'scope' => $this->scope, ]; $this->tokenExchangeParameters = [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'grant_type' => 'authorization_code', 'redirect_uri' => $this->callback ]; $refreshToken = $this->getStoredData('refresh_token'); if (!empty($refreshToken)) { $this->tokenRefreshParameters = [ 'grant_type' => 'refresh_token', 'refresh_token' => $refreshToken, ]; } $this->apiRequestHeaders = [ 'Authorization' => 'Bearer ' . $this->getStoredData('access_token') ]; } /** * {@inheritdoc} */ public function authenticate() { $this->logger->info(sprintf('%s::authenticate()', get_class($this))); if ($this->isConnected()) { return true; } try { $this->authenticateCheckError(); $code = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'code'); if (empty($code)) { $this->authenticateBegin(); } else { $this->authenticateFinish(); } } catch (Exception $e) { $this->clearStoredData(); throw $e; } return null; } /** * {@inheritdoc} */ public function isConnected() { if ((bool)$this->getStoredData('access_token')) { return (!$this->hasAccessTokenExpired() || $this->isRefreshTokenAvailable()); } return false; } /** * If we can use a refresh token, then an expired token does not stop us being connected. * * @return bool */ public function isRefreshTokenAvailable() { return is_array($this->tokenRefreshParameters); } /** * Authorization Request Error Response * * RFC6749: If the request fails due to a missing, invalid, or mismatching * redirection URI, or if the client identifier is missing or invalid, * the authorization server SHOULD inform the resource owner of the error. * * http://tools.ietf.org/html/rfc6749#section-4.1.2.1 * * @throws \Hybridauth\Exception\InvalidAuthorizationCodeException * @throws \Hybridauth\Exception\AuthorizationDeniedException */ protected function authenticateCheckError() { $error = filter_input(INPUT_GET, 'error', FILTER_SANITIZE_SPECIAL_CHARS); if (!empty($error)) { $error_description = filter_input(INPUT_GET, 'error_description', FILTER_SANITIZE_SPECIAL_CHARS); $error_uri = filter_input(INPUT_GET, 'error_uri', FILTER_SANITIZE_SPECIAL_CHARS); $collated_error = sprintf('Provider returned an error: %s %s %s', $error, $error_description, $error_uri); if ($error == 'access_denied') { throw new AuthorizationDeniedException($collated_error); } throw new InvalidAuthorizationCodeException($collated_error); } } /** * Initiate the authorization protocol * * Build Authorization URL for Authorization Request and redirect the user-agent to the * Authorization Server. */ protected function authenticateBegin() { $authUrl = $this->getAuthorizeUrl(); $this->logger->debug(sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this)), [$authUrl]); HttpClient\Util::redirect($authUrl); } /** * Finalize the authorization process * * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException * @throws InvalidAccessTokenException * @throws InvalidAuthorizationStateException */ protected function authenticateFinish() { $this->logger->debug( sprintf('%s::authenticateFinish(), callback url:', get_class($this)), [HttpClient\Util::getCurrentUrl(true)] ); $state = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'state'); $code = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'code'); /** * Authorization Request State * * RFC6749: state : RECOMMENDED. An opaque value used by the client to maintain * state between the request and callback. The authorization server includes * this value when redirecting the user-agent back to the client. * * http://tools.ietf.org/html/rfc6749#section-4.1.1 */ if ($this->supportRequestState && (!$state || $this->getStoredData('authorization_state') != $state) ) { $this->deleteStoredData('authorization_state'); throw new InvalidAuthorizationStateException( 'The authorization state [state=' . substr(htmlentities($state), 0, 100) . '] ' . 'of this page is either invalid or has already been consumed.' ); } /** * Authorization Request Code * * RFC6749: If the resource owner grants the access request, the authorization * server issues an authorization code and delivers it to the client: * * http://tools.ietf.org/html/rfc6749#section-4.1.2 */ $response = $this->exchangeCodeForAccessToken($code); $this->validateAccessTokenExchange($response); $this->initialize(); } /** * Build Authorization URL for Authorization Request * * RFC6749: The client constructs the request URI by adding the following * $parameters to the query component of the authorization endpoint URI: * * - response_type REQUIRED. Value MUST be set to "code". * - client_id REQUIRED. * - redirect_uri OPTIONAL. * - scope OPTIONAL. * - state RECOMMENDED. * * http://tools.ietf.org/html/rfc6749#section-4.1.1 * * Sub classes may redefine this method when necessary. * * @param array $parameters * * @return string Authorization URL */ protected function getAuthorizeUrl($parameters = []) { $this->AuthorizeUrlParameters = !empty($parameters) ? $parameters : array_replace( (array)$this->AuthorizeUrlParameters, (array)$this->config->get('authorize_url_parameters') ); if ($this->supportRequestState) { if (!isset($this->AuthorizeUrlParameters['state'])) { $this->AuthorizeUrlParameters['state'] = 'HA-' . str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'); } $this->storeData('authorization_state', $this->AuthorizeUrlParameters['state']); } $queryParams = http_build_query($this->AuthorizeUrlParameters, '', '&', $this->AuthorizeUrlParametersEncType); return $this->authorizeUrl . '?' . $queryParams; } /** * Access Token Request * * This method will exchange the received $code in loginFinish() with an Access Token. * * RFC6749: The client makes a request to the token endpoint by sending the * following parameters using the "application/x-www-form-urlencoded" * with a character encoding of UTF-8 in the HTTP request entity-body: * * - grant_type REQUIRED. Value MUST be set to "authorization_code". * - code REQUIRED. The authorization code received from the authorization server. * - redirect_uri REQUIRED. * - client_id REQUIRED. * * http://tools.ietf.org/html/rfc6749#section-4.1.3 * * @param string $code * * @return string Raw Provider API response * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException */ protected function exchangeCodeForAccessToken($code) { $this->tokenExchangeParameters['code'] = $code; $response = $this->httpClient->request( $this->accessTokenUrl, $this->tokenExchangeMethod, $this->tokenExchangeParameters, $this->tokenExchangeHeaders ); $this->validateApiResponse('Unable to exchange code for API access token'); return $response; } /** * Validate Access Token Response * * RFC6749: If the access token request is valid and authorized, the * authorization server issues an access token and optional refresh token. * If the request client authentication failed or is invalid, the authorization * server returns an error response as described in Section 5.2. * * Example of a successful response: * * HTTP/1.1 200 OK * Content-Type: application/json;charset=UTF-8 * Cache-Control: no-store * Pragma: no-cache * * { * "access_token":"2YotnFZFEjr1zCsicMWpAA", * "token_type":"example", * "expires_in":3600, * "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", * "example_parameter":"example_value" * } * * http://tools.ietf.org/html/rfc6749#section-4.1.4 * * This method uses Data_Parser to attempt to decodes the raw $response (usually JSON) * into a data collection. * * @param string $response * * @return \Hybridauth\Data\Collection * @throws InvalidAccessTokenException */ protected function validateAccessTokenExchange($response) { $data = (new Data\Parser())->parse($response); $collection = new Data\Collection($data); if (!$collection->exists('access_token')) { throw new InvalidAccessTokenException( 'Provider returned no access_token: ' . htmlentities($response) ); } $this->storeData('access_token', $collection->get('access_token')); $this->storeData('token_type', $collection->get('token_type')); if ($collection->get('refresh_token')) { $this->storeData('refresh_token', $collection->get('refresh_token')); } // calculate when the access token expire if ($collection->exists('expires_in')) { $expires_at = time() + (int)$collection->get('expires_in'); $this->storeData('expires_in', $collection->get('expires_in')); $this->storeData('expires_at', $expires_at); } $this->deleteStoredData('authorization_state'); $this->initialize(); return $collection; } /** * Refreshing an Access Token * * RFC6749: If the authorization server issued a refresh token to the * client, the client makes a refresh request to the token endpoint by * adding the following parameters ... in the HTTP request entity-body: * * - grant_type REQUIRED. Value MUST be set to "refresh_token". * - refresh_token REQUIRED. The refresh token issued to the client. * - scope OPTIONAL. * * http://tools.ietf.org/html/rfc6749#section-6 * * This method is similar to exchangeCodeForAccessToken(). The only * difference is here we exchange refresh_token for a new access_token. * * @param array $parameters * * @return string|null Raw Provider API response, or null if we cannot refresh * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException * @throws InvalidAccessTokenException */ public function refreshAccessToken($parameters = []) { $this->tokenRefreshParameters = !empty($parameters) ? $parameters : $this->tokenRefreshParameters; if (!$this->isRefreshTokenAvailable()) { return null; } $response = $this->httpClient->request( $this->accessTokenUrl, $this->tokenRefreshMethod, $this->tokenRefreshParameters, $this->tokenRefreshHeaders ); $this->validateApiResponse('Unable to refresh the access token'); $this->validateRefreshAccessToken($response); return $response; } /** * Check whether access token has expired * * @param int|null $time * @return bool|null */ public function hasAccessTokenExpired($time = null) { if ($time === null) { $time = time(); } $expires_at = $this->getStoredData('expires_at'); if (!$expires_at) { return null; } return $expires_at <= $time; } /** * Validate Refresh Access Token Request * * RFC6749: If valid and authorized, the authorization server issues an * access token as described in Section 5.1. If the request failed * verification or is invalid, the authorization server returns an error * response as described in Section 5.2. * * http://tools.ietf.org/html/rfc6749#section-6 * http://tools.ietf.org/html/rfc6749#section-5.1 * http://tools.ietf.org/html/rfc6749#section-5.2 * * This method simply use validateAccessTokenExchange(), however sub * classes may redefine it when necessary. * * @param $response * * @return \Hybridauth\Data\Collection * @throws InvalidAccessTokenException */ protected function validateRefreshAccessToken($response) { return $this->validateAccessTokenExchange($response); } /** * Send a signed request to provider API * * RFC6749: Accessing Protected Resources: The client accesses protected * resources by presenting the access token to the resource server. The * resource server MUST validate the access token and ensure that it has * not expired and that its scope covers the requested resource. * * Note: Since the specifics of error responses is beyond the scope of * RFC6749 and OAuth specifications, Hybridauth will consider any HTTP * status code that is different than '200 OK' as an ERROR. * * http://tools.ietf.org/html/rfc6749#section-7 * * @param string $url * @param string $method * @param array $parameters * @param array $headers * @param bool $multipart * * @return mixed * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException * @throws InvalidAccessTokenException */ public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [], $multipart = false) { // refresh tokens if needed $this->maintainToken(); if ($this->hasAccessTokenExpired() === true) { $this->refreshAccessToken(); } if (strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0) { $url = rtrim($this->apiBaseUrl, '/') . '/' . ltrim($url, '/'); } $parameters = array_replace($this->apiRequestParameters, (array)$parameters); $headers = array_replace($this->apiRequestHeaders, (array)$headers); $response = $this->httpClient->request( $url, $method, // HTTP Request Method. Defaults to GET. $parameters, // Request Parameters $headers, // Request Headers $multipart // Is request multipart ); $this->validateApiResponse('Signed API request to ' . $url . ' has returned an error'); $response = (new Data\Parser())->parse($response); return $response; } } lib/hybridauth/Adapter/OpenID.php000064400000017362147577535410012717 0ustar00config->exists('openid_identifier')) { $this->openidIdentifier = $this->config->get('openid_identifier'); } if (empty($this->openidIdentifier)) { throw new InvalidOpenidIdentifierException('OpenID adapter requires an openid_identifier.', 4); } $this->setCallback($this->config->get('callback')); $this->setApiEndpoints($this->config->get('endpoints')); } /** * {@inheritdoc} */ protected function initialize() { $hostPort = parse_url($this->callback, PHP_URL_PORT); $hostUrl = parse_url($this->callback, PHP_URL_HOST); if ($hostPort) { $hostUrl .= ':' . $hostPort; } // @fixme: add proxy $this->openIdClient = new LightOpenID($hostUrl, null); } /** * {@inheritdoc} */ public function authenticate() { $this->logger->info(sprintf('%s::authenticate()', get_class($this))); if ($this->isConnected()) { return true; } if (empty($_REQUEST['openid_mode'])) { $this->authenticateBegin(); } else { return $this->authenticateFinish(); } return null; } /** * {@inheritdoc} */ public function isConnected() { return (bool)$this->storage->get($this->providerId . '.user'); } /** * {@inheritdoc} */ public function disconnect() { $this->storage->delete($this->providerId . '.user'); return true; } /** * Initiate the authorization protocol * * Include and instantiate LightOpenID */ protected function authenticateBegin() { $this->openIdClient->identity = $this->openidIdentifier; $this->openIdClient->returnUrl = $this->callback; $this->openIdClient->required = [ 'namePerson/first', 'namePerson/last', 'namePerson/friendly', 'namePerson', 'contact/email', 'birthDate', 'birthDate/birthDay', 'birthDate/birthMonth', 'birthDate/birthYear', 'person/gender', 'pref/language', 'contact/postalCode/home', 'contact/city/home', 'contact/country/home', 'media/image/default', ]; $authUrl = $this->openIdClient->authUrl(); $this->logger->debug(sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this)), [$authUrl]); HttpClient\Util::redirect($authUrl); } /** * Finalize the authorization process. * * @throws AuthorizationDeniedException * @throws UnexpectedApiResponseException */ protected function authenticateFinish() { $this->logger->debug( sprintf('%s::authenticateFinish(), callback url:', get_class($this)), [HttpClient\Util::getCurrentUrl(true)] ); if ($this->openIdClient->mode == 'cancel') { throw new AuthorizationDeniedException('User has cancelled the authentication.'); } if (!$this->openIdClient->validate()) { throw new UnexpectedApiResponseException('Invalid response received.'); } $openidAttributes = $this->openIdClient->getAttributes(); if (!$this->openIdClient->identity) { throw new UnexpectedApiResponseException('Provider returned an unexpected response.'); } $userProfile = $this->fetchUserProfile($openidAttributes); /* with openid providers we only get user profiles once, so we store it */ $this->storage->set($this->providerId . '.user', $userProfile); } /** * Fetch user profile from received openid attributes * * @param array $openidAttributes * * @return User\Profile */ protected function fetchUserProfile($openidAttributes) { $data = new Data\Collection($openidAttributes); $userProfile = new User\Profile(); $userProfile->identifier = $this->openIdClient->identity; $userProfile->firstName = $data->get('namePerson/first'); $userProfile->lastName = $data->get('namePerson/last'); $userProfile->email = $data->get('contact/email'); $userProfile->language = $data->get('pref/language'); $userProfile->country = $data->get('contact/country/home'); $userProfile->zip = $data->get('contact/postalCode/home'); $userProfile->gender = $data->get('person/gender'); $userProfile->photoURL = $data->get('media/image/default'); $userProfile->birthDay = $data->get('birthDate/birthDay'); $userProfile->birthMonth = $data->get('birthDate/birthMonth'); $userProfile->birthYear = $data->get('birthDate/birthDate'); $userProfile = $this->fetchUserGender($userProfile, $data->get('person/gender')); $userProfile = $this->fetchUserDisplayName($userProfile, $data); return $userProfile; } /** * Extract users display names * * @param User\Profile $userProfile * @param Data\Collection $data * * @return User\Profile */ protected function fetchUserDisplayName(User\Profile $userProfile, Data\Collection $data) { $userProfile->displayName = $data->get('namePerson'); $userProfile->displayName = $userProfile->displayName ? $userProfile->displayName : $data->get('namePerson/friendly'); $userProfile->displayName = $userProfile->displayName ? $userProfile->displayName : trim($userProfile->firstName . ' ' . $userProfile->lastName); return $userProfile; } /** * Extract users gender * * @param User\Profile $userProfile * @param string $gender * * @return User\Profile */ protected function fetchUserGender(User\Profile $userProfile, $gender) { $gender = strtolower((string)$gender); if ('f' == $gender) { $gender = 'female'; } if ('m' == $gender) { $gender = 'male'; } $userProfile->gender = $gender; return $userProfile; } /** * OpenID only provide the user profile one. This method will attempt to retrieve the profile from storage. */ public function getUserProfile() { $userProfile = $this->storage->get($this->providerId . '.user'); if (!is_object($userProfile)) { throw new UnexpectedApiResponseException('Provider returned an unexpected response.'); } return $userProfile; } } lib/hybridauth/Adapter/AbstractAdapter.php000064400000021031147577535410014631 0ustar00providerId = (new \ReflectionClass($this))->getShortName(); $this->config = new Data\Collection($config); $this->setHttpClient($httpClient); $this->setStorage($storage); $this->setLogger($logger); $this->configure(); $this->logger->debug(sprintf('Initialize %s, config: ', get_class($this)), $config); $this->initialize(); } /** * Load adapter's configuration */ abstract protected function configure(); /** * Adapter initializer */ abstract protected function initialize(); /** * {@inheritdoc} */ abstract public function isConnected(); /** * {@inheritdoc} */ public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [], $multipart = false) { throw new NotImplementedException('Provider does not support this feature.'); } /** * {@inheritdoc} */ public function maintainToken() { // Nothing needed for most providers } /** * {@inheritdoc} */ public function getUserProfile() { throw new NotImplementedException('Provider does not support this feature.'); } /** * {@inheritdoc} */ public function getUserContacts() { throw new NotImplementedException('Provider does not support this feature.'); } /** * {@inheritdoc} */ public function getUserPages() { throw new NotImplementedException('Provider does not support this feature.'); } /** * {@inheritdoc} */ public function getUserActivity($stream) { throw new NotImplementedException('Provider does not support this feature.'); } /** * {@inheritdoc} */ public function setUserStatus($status) { throw new NotImplementedException('Provider does not support this feature.'); } /** * {@inheritdoc} */ public function setPageStatus($status, $pageId) { throw new NotImplementedException('Provider does not support this feature.'); } /** * {@inheritdoc} */ public function disconnect() { $this->clearStoredData(); } /** * {@inheritdoc} */ public function getAccessToken() { $tokenNames = [ 'access_token', 'access_token_secret', 'token_type', 'refresh_token', 'expires_in', 'expires_at', ]; $tokens = []; foreach ($tokenNames as $name) { if ($this->getStoredData($name)) { $tokens[$name] = $this->getStoredData($name); } } return $tokens; } /** * {@inheritdoc} */ public function setAccessToken($tokens = []) { $this->clearStoredData(); foreach ($tokens as $token => $value) { $this->storeData($token, $value); } // Re-initialize token parameters. $this->initialize(); } /** * {@inheritdoc} */ public function setHttpClient(HttpClientInterface $httpClient = null) { $this->httpClient = $httpClient ?: new HttpClient(); if ($this->config->exists('curl_options') && method_exists($this->httpClient, 'setCurlOptions')) { $this->httpClient->setCurlOptions($this->config->get('curl_options')); } } /** * {@inheritdoc} */ public function getHttpClient() { return $this->httpClient; } /** * {@inheritdoc} */ public function setStorage(StorageInterface $storage = null) { $this->storage = $storage ?: new Session(); } /** * {@inheritdoc} */ public function getStorage() { return $this->storage; } /** * {@inheritdoc} */ public function setLogger(LoggerInterface $logger = null) { $this->logger = $logger ?: new Logger( $this->config->get('debug_mode'), $this->config->get('debug_file') ); if (method_exists($this->httpClient, 'setLogger')) { $this->httpClient->setLogger($this->logger); } } /** * {@inheritdoc} */ public function getLogger() { return $this->logger; } /** * Set Adapter's API callback url * * @param string $callback * * @throws InvalidArgumentException */ protected function setCallback($callback) { if (!filter_var($callback, FILTER_VALIDATE_URL)) { throw new InvalidArgumentException('A valid callback url is required.'); } $this->callback = $callback; } /** * Overwrite Adapter's API endpoints * * @param array|Data\Collection $endpoints */ protected function setApiEndpoints($endpoints = null) { if (empty($endpoints)) { return; } $collection = is_array($endpoints) ? new Data\Collection($endpoints) : $endpoints; $this->apiBaseUrl = $collection->get('api_base_url') ?: $this->apiBaseUrl; $this->authorizeUrl = $collection->get('authorize_url') ?: $this->authorizeUrl; $this->accessTokenUrl = $collection->get('access_token_url') ?: $this->accessTokenUrl; } /** * Validate signed API responses Http status code. * * Since the specifics of error responses is beyond the scope of RFC6749 and OAuth Core specifications, * Hybridauth will consider any HTTP status code that is different than '200 OK' as an ERROR. * * @param string $error String to pre append to message thrown in exception * * @throws HttpClientFailureException * @throws HttpRequestFailedException */ protected function validateApiResponse($error = '') { $error .= !empty($error) ? '. ' : ''; if ($this->httpClient->getResponseClientError()) { throw new HttpClientFailureException( $error . 'HTTP client error: ' . $this->httpClient->getResponseClientError() . '.' ); } // if validateApiResponseHttpCode is set to false, we by pass verification of http status code if (!$this->validateApiResponseHttpCode) { return; } $status = $this->httpClient->getResponseHttpCode(); if ($status < 200 || $status > 299) { throw new HttpRequestFailedException( $error . 'HTTP error ' . $this->httpClient->getResponseHttpCode() . '. Raw Provider API response: ' . $this->httpClient->getResponseBody() . '.' ); } } } lib/hybridauth/Adapter/AdapterInterface.php000064400000006520147577535410014774 0ustar00deleteStoredData($name); } $this->getStorage()->set($this->providerId . '.' . $name, $value); } /** * Retrieve a piece of data from storage. * * This method is mainly used for OAuth tokens (access, secret, refresh, and whatnot), but it * can be also used by providers to retrieve from store any other useful data (i.g., user_id, * auth_nonce, etc.) * * @param string $name * * @return mixed */ protected function getStoredData($name) { return $this->getStorage()->get($this->providerId . '.' . $name); } /** * Delete a stored piece of data. * * @param string $name */ protected function deleteStoredData($name) { $this->getStorage()->delete($this->providerId . '.' . $name); } /** * Delete all stored data of the instantiated adapter */ protected function clearStoredData() { $this->getStorage()->deleteMatch($this->providerId . '.'); } } lib/hybridauth/Data/Parser.php000064400000004545147577535410012325 0ustar00parseJson($raw); if (!$data) { $data = $this->parseXml($raw); if (!$data) { $data = $this->parseQueryString($raw); } } return $data; } /** * Decodes a JSON string * * @param $result * * @return mixed */ public function parseJson($result) { return json_decode($result); } /** * Decodes a XML string * * @param $result * * @return mixed */ public function parseXml($result) { libxml_use_internal_errors(true); $result = preg_replace('/([<\/])([a-z0-9-]+):/i', '$1', $result); $xml = simplexml_load_string($result); libxml_use_internal_errors(false); if (!$xml) { return []; } $arr = json_decode(json_encode((array)$xml), true); $arr = array($xml->getName() => $arr); return $arr; } /** * Parses a string into variables * * @param $result * * @return \StdClass */ public function parseQueryString($result) { parse_str($result, $output); if (!is_array($output)) { return $result; } $result = new \StdClass(); foreach ($output as $k => $v) { $result->$k = $v; } return $result; } /** * needs to be improved * * @param $birthday * * @return array */ public function parseBirthday($birthday) { $birthday = date_parse((string) $birthday); return [$birthday['year'], $birthday['month'], $birthday['day']]; } } lib/hybridauth/Data/Collection.php000064400000005305147577535410013157 0ustar00collection = (object)$data; } /** * Retrieves the whole collection as array * * @return mixed */ public function toArray() { return (array)$this->collection; } /** * Retrieves an item * * @param $property * * @return mixed */ public function get($property) { if ($this->exists($property)) { return $this->collection->$property; } return null; } /** * Add or update an item * * @param $property * @param mixed $value */ public function set($property, $value) { if ($property) { $this->collection->$property = $value; } } /** * .. until I come with a better name.. * * @param $property * * @return Collection */ public function filter($property) { if ($this->exists($property)) { $data = $this->get($property); if (!is_a($data, 'Collection')) { $data = new Collection($data); } return $data; } return new Collection([]); } /** * Checks whether an item within the collection * * @param $property * * @return bool */ public function exists($property) { return property_exists($this->collection, $property); } /** * Finds whether the collection is empty * * @return bool */ public function isEmpty() { return !(bool)$this->count(); } /** * Count all items in collection * * @return int */ public function count() { return count($this->properties()); } /** * Returns all items properties names * * @return array */ public function properties() { $properties = []; foreach ($this->collection as $key => $value) { $properties[] = $key; } return $properties; } /** * Returns all items values * * @return array */ public function values() { $values = []; foreach ($this->collection as $value) { $values[] = $value; } return $values; } } lib/hybridauth/Exception/AuthorizationDeniedException.php000064400000000502147577535410017773 0ustar00getCode(); $message = $this->getMessage(); $file = $this->getFile(); $line = $this->getLine(); $trace = $this->getTraceAsString(); $html = sprintf('

%s

', $title); $html .= '

Hybridauth has encountered the following error:

'; $html .= '

Details

'; $html .= sprintf('
Exception: %s
', get_class($this)); $html .= sprintf('
Message: %s
', $message); $html .= sprintf('
File: %s
', $file); $html .= sprintf('
Line: %s
', $line); $html .= sprintf('
Code: %s
', $code); $html .= '

Trace

'; $html .= sprintf('
%s
', $trace); if ($object) { $html .= '

Debug

'; $obj_dump = print_r($object, true); // phpcs:ignore $html .= sprintf('' . get_class($object) . ' extends ' . get_parent_class($object) . '
%s
', $obj_dump); } $html .= '

Session

'; $session_dump = print_r($_SESSION, true); $html .= sprintf('
%s
', $session_dump); // phpcs:ignore echo sprintf("%s%s", $title, $html); } } lib/hybridauth/Exception/UnexpectedValueException.php000064400000001113147577535410017122 0ustar00 * $guzzle = new Hybridauth\HttpClient\Guzzle(new GuzzleHttp\Client(), [ * 'verify' => '/path/to/your/certificate.crt', * 'headers' => ['User-Agent' => '..'] * // 'proxy' => ... * ]); * * $adapter = new Hybridauth\Provider\Github($config, $guzzle); * * $adapter->authenticate(); * */ class Guzzle implements HttpClientInterface { /** * Method request() arguments * * This is used for debugging. * * @var array */ protected $requestArguments = []; /** * Default request headers * * @var array */ protected $requestHeader = []; /** * Raw response returned by server * * @var string */ protected $responseBody = ''; /** * Headers returned in the response * * @var array */ protected $responseHeader = []; /** * Response HTTP status code * * @var int */ protected $responseHttpCode = 0; /** * Last curl error number * * @var mixed */ protected $responseClientError = null; /** * Information about the last transfer * * @var mixed */ protected $responseClientInfo = []; /** * Hybridauth logger instance * * @var object */ protected $logger = null; /** * GuzzleHttp client * * @var \GuzzleHttp\Client */ protected $client = null; /** * .. * @param null $client * @param array $config */ public function __construct($client = null, $config = []) { $this->client = $client ? $client : new Client($config); } /** * {@inheritdoc} */ public function request($uri, $method = 'GET', $parameters = [], $headers = [], $multipart = false) { $this->requestHeader = array_replace($this->requestHeader, (array)$headers); $this->requestArguments = [ 'uri' => $uri, 'method' => $method, 'parameters' => $parameters, 'headers' => $this->requestHeader, ]; $response = null; try { switch ($method) { case 'GET': case 'DELETE': $response = $this->client->request($method, $uri, [ 'query' => $parameters, 'headers' => $this->requestHeader, ]); break; case 'PUT': case 'PATCH': case 'POST': $body_type = $multipart ? 'multipart' : 'form_params'; if (isset($this->requestHeader['Content-Type']) && $this->requestHeader['Content-Type'] === 'application/json' ) { $body_type = 'json'; } $body_content = $parameters; if ($multipart) { $body_content = []; foreach ($parameters as $key => $val) { if ($val instanceof \CURLFile) { $val = fopen($val->getFilename(), 'r'); } $body_content[] = [ 'name' => $key, 'contents' => $val, ]; } } $response = $this->client->request($method, $uri, [ $body_type => $body_content, 'headers' => $this->requestHeader, ]); break; } } catch (\Exception $e) { $response = $e->getResponse(); $this->responseClientError = $e->getMessage(); } if (!$this->responseClientError) { $this->responseBody = $response->getBody(); $this->responseHttpCode = $response->getStatusCode(); $this->responseHeader = $response->getHeaders(); } if ($this->logger) { // phpcs:ignore $this->logger->debug(sprintf('%s::request( %s, %s ), response:', get_class($this), $uri, $method), $this->getResponse()); if ($this->responseClientError) { // phpcs:ignore $this->logger->error(sprintf('%s::request( %s, %s ), error:', get_class($this), $uri, $method), [$this->responseClientError]); } } return $this->responseBody; } /** * Get response details * * @return array Map structure of details */ public function getResponse() { return [ 'request' => $this->getRequestArguments(), 'response' => [ 'code' => $this->getResponseHttpCode(), 'headers' => $this->getResponseHeader(), 'body' => $this->getResponseBody(), ], 'client' => [ 'error' => $this->getResponseClientError(), 'info' => $this->getResponseClientInfo(), 'opts' => null, ], ]; } /** * Set logger instance * * @param object $logger */ public function setLogger($logger) { $this->logger = $logger; } /** * {@inheritdoc} */ public function getResponseBody() { return $this->responseBody; } /** * {@inheritdoc} */ public function getResponseHeader() { return $this->responseHeader; } /** * {@inheritdoc} */ public function getResponseHttpCode() { return $this->responseHttpCode; } /** * {@inheritdoc} */ public function getResponseClientError() { return $this->responseClientError; } /** * @return array */ protected function getResponseClientInfo() { return $this->responseClientInfo; } /** * Returns method request() arguments * * This is used for debugging. * * @return array */ protected function getRequestArguments() { return $this->requestArguments; } } lib/hybridauth/HttpClient/Util.php000064400000004773147577535410013216 0ustar00get('HTTPS') && $collection->get('HTTPS') !== 'off') || $collection->get('HTTP_X_FORWARDED_PROTO') === 'https') { $protocol = 'https://'; } return $protocol . $collection->get('HTTP_HOST') . $collection->get($requestUri ? 'REQUEST_URI' : 'PHP_SELF'); } } lib/hybridauth/HttpClient/HttpClientInterface.php000064400000002506147577535410016170 0ustar00 30, CURLOPT_CONNECTTIMEOUT => 30, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 5, CURLINFO_HEADER_OUT => true, CURLOPT_ENCODING => 'identity', // phpcs:ignore CURLOPT_USERAGENT => 'Hybridauth, PHP Social Authentication Library (https://github.com/hybridauth/hybridauth)', ]; /** * Method request() arguments * * This is used for debugging. * * @var array */ protected $requestArguments = []; /** * Default request headers * * @var array */ protected $requestHeader = [ 'Accept' => '*/*', 'Cache-Control' => 'max-age=0', 'Connection' => 'keep-alive', 'Expect' => '', 'Pragma' => '', ]; /** * Raw response returned by server * * @var string */ protected $responseBody = ''; /** * Headers returned in the response * * @var array */ protected $responseHeader = []; /** * Response HTTP status code * * @var int */ protected $responseHttpCode = 0; /** * Last curl error number * * @var mixed */ protected $responseClientError = null; /** * Information about the last transfer * * @var mixed */ protected $responseClientInfo = []; /** * Hybridauth logger instance * * @var object */ protected $logger = null; /** * {@inheritdoc} */ public function request($uri, $method = 'GET', $parameters = [], $headers = [], $multipart = false) { $this->requestHeader = array_replace($this->requestHeader, (array)$headers); $this->requestArguments = [ 'uri' => $uri, 'method' => $method, 'parameters' => $parameters, 'headers' => $this->requestHeader, ]; $curl = curl_init(); switch ($method) { case 'GET': case 'DELETE': unset($this->curlOptions[CURLOPT_POST]); unset($this->curlOptions[CURLOPT_POSTFIELDS]); $uri = $uri . (strpos($uri, '?') ? '&' : '?') . http_build_query($parameters); if ($method === 'DELETE') { $this->curlOptions[CURLOPT_CUSTOMREQUEST] = 'DELETE'; } break; case 'PUT': case 'POST': case 'PATCH': $body_content = $multipart ? $parameters : http_build_query($parameters); if (isset($this->requestHeader['Content-Type']) && $this->requestHeader['Content-Type'] == 'application/json' ) { $body_content = json_encode($parameters); } if ($method === 'POST') { $this->curlOptions[CURLOPT_POST] = true; } else { $this->curlOptions[CURLOPT_CUSTOMREQUEST] = $method; } $this->curlOptions[CURLOPT_POSTFIELDS] = $body_content; break; } $this->curlOptions[CURLOPT_URL] = $uri; $this->curlOptions[CURLOPT_HTTPHEADER] = $this->prepareRequestHeaders(); $this->curlOptions[CURLOPT_HEADERFUNCTION] = [$this, 'fetchResponseHeader']; foreach ($this->curlOptions as $opt => $value) { curl_setopt($curl, $opt, $value); } $response = curl_exec($curl); $this->responseBody = $response; $this->responseHttpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); $this->responseClientError = curl_error($curl); $this->responseClientInfo = curl_getinfo($curl); if ($this->logger) { // phpcs:ignore $this->logger->debug(sprintf('%s::request( %s, %s ), response:', get_class($this), $uri, $method), $this->getResponse()); if (false === $response) { // phpcs:ignore $this->logger->error(sprintf('%s::request( %s, %s ), error:', get_class($this), $uri, $method), [$this->responseClientError]); } } curl_close($curl); return $this->responseBody; } /** * Get response details * * @return array Map structure of details */ public function getResponse() { $curlOptions = $this->curlOptions; $curlOptions[CURLOPT_HEADERFUNCTION] = '*omitted'; return [ 'request' => $this->getRequestArguments(), 'response' => [ 'code' => $this->getResponseHttpCode(), 'headers' => $this->getResponseHeader(), 'body' => $this->getResponseBody(), ], 'client' => [ 'error' => $this->getResponseClientError(), 'info' => $this->getResponseClientInfo(), 'opts' => $curlOptions, ], ]; } /** * Reset curl options * * @param array $curlOptions */ public function setCurlOptions($curlOptions) { foreach ($curlOptions as $opt => $value) { $this->curlOptions[$opt] = $value; } } /** * Set logger instance * * @param object $logger */ public function setLogger($logger) { $this->logger = $logger; } /** * {@inheritdoc} */ public function getResponseBody() { return $this->responseBody; } /** * {@inheritdoc} */ public function getResponseHeader() { return $this->responseHeader; } /** * {@inheritdoc} */ public function getResponseHttpCode() { return $this->responseHttpCode; } /** * {@inheritdoc} */ public function getResponseClientError() { return $this->responseClientError; } /** * @return array */ protected function getResponseClientInfo() { return $this->responseClientInfo; } /** * Returns method request() arguments * * This is used for debugging. * * @return array */ protected function getRequestArguments() { return $this->requestArguments; } /** * Fetch server response headers * * @param mixed $curl * @param string $header * * @return int */ protected function fetchResponseHeader($curl, $header) { $pos = strpos($header, ':'); if (!empty($pos)) { $key = str_replace('-', '_', strtolower(substr($header, 0, $pos))); $value = trim(substr($header, $pos + 2)); $this->responseHeader[$key] = $value; } return strlen($header); } /** * Convert request headers to the expect curl format * * @return array */ protected function prepareRequestHeaders() { $headers = []; foreach ($this->requestHeader as $header => $value) { $headers[] = trim($header) . ': ' . trim($value); } return $headers; } } lib/hybridauth/Logger/Logger.php000064400000006276147577535410012661 0ustar00level !== Logger::NONE. * * @var string */ protected $file; /** * @param bool|string $level One of Logger::NONE, Logger::DEBUG, Logger::INFO, Logger::ERROR * @param string $file File where to write messages * * @throws InvalidArgumentException * @throws RuntimeException */ public function __construct($level, $file) { $this->level = self::NONE; if ($level && $level !== self::NONE) { $this->initialize($file); $this->level = $level === true ? Logger::DEBUG : $level; $this->file = $file; } } /** * @param string $file * * @throws InvalidArgumentException * @throws RuntimeException */ protected function initialize($file) { if (!$file) { throw new InvalidArgumentException('Log file is not specified.'); } if (!file_exists($file) && !touch($file)) { throw new RuntimeException(sprintf('Log file %s can not be created.', $file)); } if (!is_writable($file)) { throw new RuntimeException(sprintf('Log file %s is not writeable.', $file)); } } /** * @inheritdoc */ public function info($message, array $context = []) { if (!in_array($this->level, [self::DEBUG, self::INFO])) { return; } $this->log(self::INFO, $message, $context); } /** * @inheritdoc */ public function debug($message, array $context = []) { if (!in_array($this->level, [self::DEBUG])) { return; } $this->log(self::DEBUG, $message, $context); } /** * @inheritdoc */ public function error($message, array $context = []) { if (!in_array($this->level, [self::DEBUG, self::INFO, self::ERROR])) { return; } $this->log(self::ERROR, $message, $context); } /** * @inheritdoc */ public function log($level, $message, array $context = []) { $datetime = new \DateTime(); $datetime = $datetime->format(DATE_ATOM); $content = sprintf('%s -- %s -- %s -- %s', $level, $_SERVER['REMOTE_ADDR'], $datetime, $message); $content .= ($context ? "\n" . print_r($context, true) : ''); $content .= "\n"; file_put_contents($this->file, $content, FILE_APPEND); } } lib/hybridauth/Logger/Psr3LoggerWrapper.php000064400000001730147577535410014760 0ustar00logger->info($message, $context); } /** * @inheritdoc */ public function debug($message, array $context = []) { $this->logger->debug($message, $context); } /** * @inheritdoc */ public function error($message, array $context = []) { $this->logger->error($message, $context); } /** * @inheritdoc */ public function log($level, $message, array $context = []) { $this->logger->log($level, $message, $context); } } lib/hybridauth/Logger/LoggerInterface.php000064400000002221147577535410014464 0ustar00 Hybridauth\HttpClient\Util::getCurrentUrl(), * * // authenticate with Yahoo openid * 'openid_identifier' => 'https://open.login.yahooapis.com/openid20/www.yahoo.com/xrds' * * // authenticate with stackexchange network openid * // 'openid_identifier' => 'https://openid.stackexchange.com/', * * // authenticate with Steam openid * // 'openid_identifier' => 'http://steamcommunity.com/openid', * * // etc. * ]; * * $adapter = new Hybridauth\Provider\OpenID($config); * * try { * $adapter->authenticate(); * * $userProfile = $adapter->getUserProfile(); * } catch (\Exception $e) { * echo $e->getMessage() ; * } */ class OpenID extends Adapter\OpenID { } lib/hybridauth/Provider/LinkedInOpenID.php000064400000004175147577535410014545 0ustar00isRefreshTokenAvailable()) { $this->tokenRefreshParameters += [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret ]; } } /** * {@inheritdoc} */ public function getUserProfile() { $response = $this->apiRequest('/userinfo', 'GET', []); $data = new Data\Collection($response); if (!$data->exists('sub')) { throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); } $userProfile = new User\Profile(); $userProfile->firstName = $data->get('given_name'); $userProfile->lastName = $data->get('family_name'); $userProfile->identifier = $data->get('sub'); $userProfile->email = $data->get('email'); $userProfile->emailVerified = $data->get('email_verified'); $userProfile->displayName = $data->get('name'); $userProfile->photoURL = $data->get('picture'); return $userProfile; } } lib/hybridauth/Storage/StorageInterface.php000064400000001615147577535410015044 0ustar00keyPrefix . strtolower($key); if (isset($_SESSION[$this->storeNamespace], $_SESSION[$this->storeNamespace][$key])) { $value = $_SESSION[$this->storeNamespace][$key]; if (is_array($value) && array_key_exists('lateObject', $value)) { $value = unserialize($value['lateObject']); } return $value; } return null; } /** * {@inheritdoc} */ public function set($key, $value) { $key = $this->keyPrefix . strtolower($key); if (is_object($value)) { // We encapsulate as our classes may be defined after session is initialized. $value = ['lateObject' => serialize($value)]; } $_SESSION[$this->storeNamespace][$key] = $value; } /** * {@inheritdoc} */ public function clear() { $_SESSION[$this->storeNamespace] = []; } /** * {@inheritdoc} */ public function delete($key) { $key = $this->keyPrefix . strtolower($key); if (isset($_SESSION[$this->storeNamespace], $_SESSION[$this->storeNamespace][$key])) { $tmp = $_SESSION[$this->storeNamespace]; unset($tmp[$key]); $_SESSION[$this->storeNamespace] = $tmp; } } /** * {@inheritdoc} */ public function deleteMatch($key) { $key = $this->keyPrefix . strtolower($key); if (isset($_SESSION[$this->storeNamespace]) && count($_SESSION[$this->storeNamespace])) { $tmp = $_SESSION[$this->storeNamespace]; foreach ($tmp as $k => $v) { if (strstr($k, $key)) { unset($tmp[$k]); } } $_SESSION[$this->storeNamespace] = $tmp; } } } lib/hybridauth/Thirdparty/OAuth/OAuthSignatureMethod.php000064400000003361147577535410017430 0ustar00build_signature($request, $consumer, $token); // Check for zero length, although unlikely here if (strlen($built) == 0 || strlen($signature) == 0) { return false; } if (strlen($built) != strlen($signature)) { return false; } // Avoid a timing leak with a (hopefully) time insensitive compare $result = 0; for ($i = 0; $i < strlen($signature); $i ++) { $result |= ord($built[$i]) ^ ord($signature[$i]); } return $result == 0; } } lib/hybridauth/Thirdparty/OAuth/README.md000064400000000320147577535410014123 0ustar00This package contains OAuth PHP Library. OAuth PHP Library is an open source software available under the MIT License. https://code.google.com/p/oauth/ http://oauth.googlecode.com/svn/code/php/LICENSE.txt lib/hybridauth/Thirdparty/OAuth/OAuthRequest.php000064400000024106147577535410015756 0ustar00parameters = $parameters; $this->http_method = $http_method; $this->http_url = $http_url; } /** * attempt to build up a request from what was passed to the server * * @param null $http_method * @param null $http_url * @param null $parameters * * @return OAuthRequest */ public static function from_request($http_method = null, $http_url = null, $parameters = null) { $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") ? 'http' : 'https'; $http_url = ($http_url) ? $http_url : $scheme . '://' . $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'] . $_SERVER['REQUEST_URI']; $http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD']; // We weren't handed any parameters, so let's find the ones relevant to // this request. // If you run XML-RPC or similar you should use this to provide your own // parsed parameter-list if (!$parameters) { // Find request headers $request_headers = OAuthUtil::get_headers(); // Parse the query-string to find GET parameters $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']); // It's a POST request of the proper content-type, so parse POST // parameters and add those overriding any duplicates from GET if ($http_method == "POST" && isset($request_headers['Content-Type']) && strstr($request_headers['Content-Type'], 'application/x-www-form-urlencoded')) { $post_data = OAuthUtil::parse_parameters(file_get_contents(self::$POST_INPUT)); $parameters = array_merge($parameters, $post_data); } // We have a Authorization-header with OAuth data. Parse the header // and add those overriding any duplicates from GET or POST if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') { $header_parameters = OAuthUtil::split_header($request_headers['Authorization']); $parameters = array_merge($parameters, $header_parameters); } } return new OAuthRequest($http_method, $http_url, $parameters); } /** * pretty much a helper function to set up the request * @param $consumer * @param $token * @param $http_method * @param $http_url * @param null $parameters * @return OAuthRequest */ public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters = null) { $parameters = ($parameters) ? $parameters : array(); $defaults = array( "oauth_version" => OAuthRequest::$version, "oauth_nonce" => OAuthRequest::generate_nonce(), "oauth_timestamp" => OAuthRequest::generate_timestamp(), "oauth_consumer_key" => $consumer->key ); if ($token) { $defaults['oauth_token'] = $token->key; } $parameters = array_merge($defaults, $parameters); return new OAuthRequest($http_method, $http_url, $parameters); } /** * @param $name * @param $value * @param bool $allow_duplicates */ public function set_parameter($name, $value, $allow_duplicates = true) { if ($allow_duplicates && isset($this->parameters[$name])) { // We have already added parameter(s) with this name, so add to the list if (is_scalar($this->parameters[$name])) { // This is the first duplicate, so transform scalar (string) // into an array so we can add the duplicates $this->parameters[$name] = array( $this->parameters[$name] ); } $this->parameters[$name][] = $value; } else { $this->parameters[$name] = $value; } } /** * @param $name * * @return |null */ public function get_parameter($name) { return isset($this->parameters[$name]) ? $this->parameters[$name] : null; } /** * @return array */ public function get_parameters() { return $this->parameters; } /** * @param $name */ public function unset_parameter($name) { unset($this->parameters[$name]); } /** * The request parameters, sorted and concatenated into a normalized string. * * @return string */ public function get_signable_parameters() { $params = []; // Grab all parameters. foreach ($this->parameters as $key_param => $value_param) { // Process only scalar values. if (is_scalar($value_param)) { $params[$key_param] = $value_param; } } // Remove oauth_signature if present // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.") if (isset($params['oauth_signature'])) { unset($params['oauth_signature']); } return OAuthUtil::build_http_query($params); } /** * Returns the base string of this request * * The base string defined as the method, the url * and the parameters (normalized), each urlencoded * and the concated with &. */ public function get_signature_base_string() { $parts = array( $this->get_normalized_http_method(), $this->get_normalized_http_url(), $this->get_signable_parameters() ); $parts = OAuthUtil::urlencode_rfc3986($parts); return implode('&', $parts); } /** * just uppercases the http method */ public function get_normalized_http_method() { return strtoupper($this->http_method); } /** * parses the url and rebuilds it to be * scheme://host/path */ public function get_normalized_http_url() { $parts = parse_url($this->http_url); $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http'; $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80'); $host = (isset($parts['host'])) ? strtolower($parts['host']) : ''; $path = (isset($parts['path'])) ? $parts['path'] : ''; if (($scheme == 'https' && $port != '443') || ($scheme == 'http' && $port != '80')) { $host = "$host:$port"; } return "$scheme://$host$path"; } /** * builds a url usable for a GET request */ public function to_url() { $post_data = $this->to_postdata(); $out = $this->get_normalized_http_url(); if ($post_data) { $out .= '?' . $post_data; } return $out; } /** * builds the data one would send in a POST request */ public function to_postdata() { return OAuthUtil::build_http_query($this->parameters); } /** * builds the Authorization: header * @param null $realm * @return array */ public function to_header($realm = null) { $first = true; if ($realm) { $out = 'OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"'; $first = false; } else { $out = 'OAuth'; } foreach ($this->parameters as $k => $v) { if (substr($k, 0, 5) != "oauth") { continue; } if (is_array($v)) { continue; } $out .= ($first) ? ' ' : ','; $out .= OAuthUtil::urlencode_rfc3986($k) . '="' . OAuthUtil::urlencode_rfc3986($v) . '"'; $first = false; } return array( 'Authorization' => $out ); //- hacked into this to make it return an array. 15/11/2014. } /** * @return string */ public function __toString() { return $this->to_url(); } /** * @param $signature_method * @param $consumer * @param $token */ public function sign_request($signature_method, $consumer, $token) { $this->set_parameter("oauth_signature_method", $signature_method->get_name(), false); $signature = $this->build_signature($signature_method, $consumer, $token); $this->set_parameter("oauth_signature", $signature, false); } /** * @param $signature_method * @param $consumer * @param $token * * @return mixed */ public function build_signature($signature_method, $consumer, $token) { $signature = $signature_method->build_signature($this, $consumer, $token); return $signature; } /** * util function: current timestamp */ private static function generate_timestamp() { return time(); } /** * util function: current nonce */ private static function generate_nonce() { $mt = microtime(); $rand = mt_rand(); return md5($mt . $rand); // md5s look nicer than numbers } } lib/hybridauth/Thirdparty/OAuth/OAuthSignatureMethodHMACSHA1.php000064400000002034147577535410020472 0ustar00get_signature_base_string(); $request->base_string = $base_string; $key_parts = array( $consumer->secret, $token ? $token->secret : '' ); $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); $key = implode('&', $key_parts); return base64_encode(hash_hmac('sha1', $base_string, $key, true)); } } lib/hybridauth/Thirdparty/OAuth/OAuthConsumer.php000064400000001540147577535410016116 0ustar00key = $key; $this->secret = $secret; $this->callback_url = $callback_url; } /** * @return string */ public function __toString() { return "OAuthConsumer[key=$this->key,secret=$this->secret]"; } } lib/hybridauth/Thirdparty/OAuth/OAuthUtil.php000064400000015636147577535410015253 0ustar00 $h) { $params[$h] = OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]); } if (isset($params['realm'])) { unset($params['realm']); } } return $params; } // helper to try to sort out headers for people who aren't running apache /** * @return array */ public static function get_headers() { if (function_exists('apache_request_headers')) { // we need this to get the actual Authorization: header // because apache tends to tell us it doesn't exist $headers = apache_request_headers(); // sanitize the output of apache_request_headers because // we always want the keys to be Cased-Like-This and arh() // returns the headers in the same case as they are in the // request $out = array(); foreach ($headers as $key => $value) { $key = str_replace(" ", "-", ucwords(strtolower(str_replace("-", " ", $key)))); $out[$key] = $value; } } else { // otherwise we don't have apache and are just going to have to hope // that $_SERVER actually contains what we need $out = array(); if (isset($_SERVER['CONTENT_TYPE'])) { $out['Content-Type'] = $_SERVER['CONTENT_TYPE']; } if (isset($_ENV['CONTENT_TYPE'])) { $out['Content-Type'] = $_ENV['CONTENT_TYPE']; } foreach ($_SERVER as $key => $value) { if (substr($key, 0, 5) == "HTTP_") { // this is chaos, basically it is just there to capitalize the first // letter of every word that is not an initial HTTP and strip HTTP // code from przemek $key = str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5))))); $out[$key] = $value; } } } return $out; } // This function takes a input like a=b&a=c&d=e and returns the parsed // parameters like this // array('a' => array('b','c'), 'd' => 'e') /** * @param $input * * @return array */ public static function parse_parameters($input) { if (!isset($input) || !$input) { return array(); } $pairs = explode('&', $input); $parsed_parameters = array(); foreach ($pairs as $pair) { $split = explode('=', $pair, 2); $parameter = OAuthUtil::urldecode_rfc3986($split[0]); $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : ''; if (isset($parsed_parameters[$parameter])) { // We have already recieved parameter(s) with this name, so add to the list // of parameters with this name if (is_scalar($parsed_parameters[$parameter])) { // This is the first duplicate, so transform scalar (string) into an array // so we can add the duplicates $parsed_parameters[$parameter] = array( $parsed_parameters[$parameter] ); } $parsed_parameters[$parameter][] = $value; } else { $parsed_parameters[$parameter] = $value; } } return $parsed_parameters; } /** * @param $params * * @return string */ public static function build_http_query($params) { if (!$params) { return ''; } // Urlencode both keys and values $keys = OAuthUtil::urlencode_rfc3986(array_keys($params)); $values = OAuthUtil::urlencode_rfc3986(array_values($params)); $params = array_combine($keys, $values); // Parameters are sorted by name, using lexicographical byte value ordering. // Ref: Spec: 9.1.1 (1) uksort($params, 'strcmp'); $pairs = array(); foreach ($params as $parameter => $value) { if (is_array($value)) { // If two or more parameters share the same name, they are sorted by their value // Ref: Spec: 9.1.1 (1) // June 12th, 2010 - changed to sort because of issue 164 by hidetaka sort($value, SORT_STRING); foreach ($value as $duplicate_value) { $pairs[] = $parameter . '=' . $duplicate_value; } } else { $pairs[] = $parameter . '=' . $value; } } // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61) // Each name-value pair is separated by an '&' character (ASCII code 38) return implode('&', $pairs); } } lib/hybridauth/Thirdparty/OpenID/README.md000064400000000323147577535410014224 0ustar00This file is part of the LightOpenID PHP Library LightOpenID is an open source software available under the MIT License. https://github.com/iignatov/LightOpenID http://opensource.org/licenses/mit-license.php lib/hybridauth/Thirdparty/OpenID/LightOpenID.php000064400000127653147577535410015604 0ustar00= 5.1.2 with cURL or HTTP/HTTPS stream wrappers enabled. * * @version v1.3.1 (2016-03-04) * @link https://code.google.com/p/lightopenid/ Project URL * @link https://github.com/iignatov/LightOpenID GitHub Repo * @author Mewp * @copyright Copyright (c) 2013 Mewp * @license http://opensource.org/licenses/mit-license.php MIT License */ class LightOpenID { public $returnUrl ; public $required = array() ; public $optional = array() ; public $verify_peer = null ; public $capath = null ; public $cainfo = null ; public $cnmatch = null ; public $data ; public $oauth = array() ; public $curl_time_out = 30 // in seconds ; public $curl_connect_time_out = 30; // in seconds private $identity; private $claimed_id; protected $server; protected $version; protected $trustRoot; protected $aliases; protected $identifier_select = false ; protected $ax = false; protected $sreg = false; protected $setup_url = null; protected $headers = array() ; protected $proxy = null; protected $user_agent = 'LightOpenID' ; protected $xrds_override_pattern = null; protected $xrds_override_replacement = null; protected static $ax_to_sreg = array( 'namePerson/friendly' => 'nickname', 'contact/email' => 'email', 'namePerson' => 'fullname', 'birthDate' => 'dob', 'person/gender' => 'gender', 'contact/postalCode/home' => 'postcode', 'contact/country/home' => 'country', 'pref/language' => 'language', 'pref/timezone' => 'timezone', ); /** * LightOpenID constructor. * * @param $host * @param null $proxy * * @throws ErrorException */ public function __construct($host, $proxy = null) { $this->set_realm($host); $this->set_proxy($proxy); $uri = rtrim(preg_replace('#((?<=\?)|&)openid\.[^&]+#', '', $_SERVER['REQUEST_URI']), '?'); $this->returnUrl = $this->trustRoot . $uri; $this->data = ($_SERVER['REQUEST_METHOD'] === 'POST') ? $_POST : $_GET; if (!function_exists('curl_init') && !in_array('https', stream_get_wrappers())) { throw new ErrorException('You must have either https wrappers or curl enabled.'); } } /** * @param $name * * @return bool */ public function __isset($name) { return in_array($name, array('identity', 'trustRoot', 'realm', 'xrdsOverride', 'mode')); } /** * @param $name * @param $value */ public function __set($name, $value) { switch ($name) { case 'identity': if (strlen($value = trim((String) $value))) { if (preg_match('#^xri:/*#i', $value, $m)) { $value = substr($value, strlen($m[0])); } elseif (!preg_match('/^(?:[=@+\$!\(]|https?:)/i', $value)) { $value = "http://$value"; } if (preg_match('#^https?://[^/]+$#i', $value, $m)) { $value .= '/'; } } $this->$name = $this->claimed_id = $value; break; case 'trustRoot': case 'realm': $this->trustRoot = trim($value); break; case 'xrdsOverride': if (is_array($value)) { list($pattern, $replacement) = $value; $this->xrds_override_pattern = $pattern; $this->xrds_override_replacement = $replacement; } else { trigger_error('Invalid value specified for "xrdsOverride".', E_USER_ERROR); } break; } } /** * @param $name * * @return |null */ public function __get($name) { switch ($name) { case 'identity': # We return claimed_id instead of identity, # because the developer should see the claimed identifier, # i.e. what he set as identity, not the op-local identifier (which is what we verify) return $this->claimed_id; case 'trustRoot': case 'realm': return $this->trustRoot; case 'mode': return empty($this->data['openid_mode']) ? null : $this->data['openid_mode']; } } /** * @param $proxy * * @throws ErrorException */ public function set_proxy($proxy) { if (!empty($proxy)) { // When the proxy is a string - try to parse it. if (!is_array($proxy)) { $proxy = parse_url($proxy); } // Check if $proxy is valid after the parsing. if ($proxy && !empty($proxy['host'])) { // Make sure that a valid port number is specified. if (array_key_exists('port', $proxy)) { if (!is_int($proxy['port'])) { $proxy['port'] = is_numeric($proxy['port']) ? intval($proxy['port']) : 0; } if ($proxy['port'] <= 0) { throw new ErrorException('The specified proxy port number is invalid.'); } } $this->proxy = $proxy; } } } /** * Checks if the server specified in the url exists. * * @param $url string url to check * @return true, if the server exists; false otherwise */ public function hostExists($url) { if (strpos($url, '/') === false) { $server = $url; } else { $server = @parse_url($url, PHP_URL_HOST); } if (!$server) { return false; } return !!gethostbynamel($server); } /** * @param $uri */ protected function set_realm($uri) { $realm = ''; # Set a protocol, if not specified. $realm .= (($offset = strpos($uri, '://')) === false) ? $this->get_realm_protocol() : ''; # Set the offset properly. $offset = (($offset !== false) ? $offset + 3 : 0); # Get only the root, without the path. $realm .= (($end = strpos($uri, '/', $offset)) === false) ? $uri : substr($uri, 0, $end); $this->trustRoot = $realm; } /** * @return string */ protected function get_realm_protocol() { if (!empty($_SERVER['HTTPS'])) { $use_secure_protocol = ($_SERVER['HTTPS'] !== 'off'); } elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { $use_secure_protocol = ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'); } elseif (isset($_SERVER['HTTP__WSSC'])) { $use_secure_protocol = ($_SERVER['HTTP__WSSC'] == 'https'); } else { $use_secure_protocol = false; } return $use_secure_protocol ? 'https://' : 'http://'; } /** * @param $url * @param string $method * @param array $params * @param $update_claimed_id * * @return array|bool|string * @throws ErrorException */ protected function request_curl($url, $method='GET', $params=array(), $update_claimed_id=false) { $params = http_build_query($params, '', '&'); $curl = curl_init($url . ($method == 'GET' && $params ? '?' . $params : '')); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); curl_setopt($curl, CURLOPT_HEADER, false); curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); if ($method == 'POST') { curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded')); } else { curl_setopt($curl, CURLOPT_HTTPHEADER, array('Accept: application/xrds+xml, */*')); } curl_setopt($curl, CURLOPT_TIMEOUT, $this->curl_time_out); // defaults to infinite curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $this->curl_connect_time_out); // defaults to 300s if (!empty($this->proxy)) { curl_setopt($curl, CURLOPT_PROXY, $this->proxy['host']); if (!empty($this->proxy['port'])) { curl_setopt($curl, CURLOPT_PROXYPORT, $this->proxy['port']); } if (!empty($this->proxy['user'])) { curl_setopt($curl, CURLOPT_PROXYUSERPWD, $this->proxy['user'] . ':' . $this->proxy['pass']); } } if ($this->verify_peer !== null) { curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verify_peer); if ($this->capath) { curl_setopt($curl, CURLOPT_CAPATH, $this->capath); } if ($this->cainfo) { curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo); } } if ($method == 'POST') { curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_POSTFIELDS, $params); } elseif ($method == 'HEAD') { curl_setopt($curl, CURLOPT_HEADER, true); curl_setopt($curl, CURLOPT_NOBODY, true); } else { curl_setopt($curl, CURLOPT_HEADER, true); curl_setopt($curl, CURLOPT_HTTPGET, true); } $response = curl_exec($curl); if ($method == 'HEAD' && curl_getinfo($curl, CURLINFO_HTTP_CODE) == 405) { curl_setopt($curl, CURLOPT_HTTPGET, true); $response = curl_exec($curl); $response = substr($response, 0, strpos($response, "\r\n\r\n")); } if ($method == 'HEAD' || $method == 'GET') { $header_response = $response; # If it's a GET request, we want to only parse the header part. if ($method == 'GET') { $header_response = substr($response, 0, strpos($response, "\r\n\r\n")); } $headers = array(); foreach (explode("\n", $header_response) as $header) { $pos = strpos($header, ':'); if ($pos !== false) { $name = strtolower(trim(substr($header, 0, $pos))); $headers[$name] = trim(substr($header, $pos+1)); } } if ($update_claimed_id) { # Update the claimed_id value in case of redirections. $effective_url = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL); # Ignore the fragment (some cURL versions don't handle it well). if (strtok($effective_url, '#') != strtok($url, '#')) { $this->identity = $this->claimed_id = $effective_url; } } if ($method == 'HEAD') { return $headers; } else { $this->headers = $headers; } } if (curl_errno($curl)) { throw new ErrorException(curl_error($curl), curl_errno($curl)); } return $response; } /** * @param $array * @param $update_claimed_id * * @return array */ protected function parse_header_array($array, $update_claimed_id) { $headers = array(); foreach ($array as $header) { $pos = strpos($header, ':'); if ($pos !== false) { $name = strtolower(trim(substr($header, 0, $pos))); $headers[$name] = trim(substr($header, $pos+1)); # Following possible redirections. The point is just to have # claimed_id change with them, because the redirections # are followed automatically. # We ignore redirections with relative paths. # If any known provider uses them, file a bug report. if ($name == 'location' && $update_claimed_id) { if (strpos($headers[$name], 'http') === 0) { $this->identity = $this->claimed_id = $headers[$name]; } elseif ($headers[$name][0] == '/') { $parsed_url = parse_url($this->claimed_id); $this->identity = $this->claimed_id = $parsed_url['scheme'] . '://' . $parsed_url['host'] . $headers[$name]; } } } } return $headers; } /** * @param $url * @param string $method * @param array $params * @param $update_claimed_id * * @return array|false|string * @throws ErrorException */ protected function request_streams($url, $method='GET', $params=array(), $update_claimed_id=false) { if (!$this->hostExists($url)) { throw new ErrorException("Could not connect to $url.", 404); } if (empty($this->cnmatch)) { $this->cnmatch = parse_url($url, PHP_URL_HOST); } $params = http_build_query($params, '', '&'); switch ($method) { case 'GET': $opts = array( 'http' => array( 'method' => 'GET', 'header' => 'Accept: application/xrds+xml, */*', 'user_agent' => $this->user_agent, 'ignore_errors' => true, ), 'ssl' => array( 'CN_match' => $this->cnmatch ) ); $url = $url . ($params ? '?' . $params : ''); if (!empty($this->proxy)) { $opts['http']['proxy'] = $this->proxy_url(); } break; case 'POST': $opts = array( 'http' => array( 'method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', 'user_agent' => $this->user_agent, 'content' => $params, 'ignore_errors' => true, ), 'ssl' => array( 'CN_match' => $this->cnmatch ) ); if (!empty($this->proxy)) { $opts['http']['proxy'] = $this->proxy_url(); } break; case 'HEAD': // We want to send a HEAD request, but since get_headers() doesn't // accept $context parameter, we have to change the defaults. $default = stream_context_get_options(stream_context_get_default()); // PHP does not reset all options. Instead, it just sets the options // available in the passed array, therefore set the defaults manually. $default += array( 'http' => array(), 'ssl' => array() ); $default['http'] += array( 'method' => 'GET', 'header' => '', 'user_agent' => '', 'ignore_errors' => false ); $default['ssl'] += array( 'CN_match' => '' ); $opts = array( 'http' => array( 'method' => 'HEAD', 'header' => 'Accept: application/xrds+xml, */*', 'user_agent' => $this->user_agent, 'ignore_errors' => true, ), 'ssl' => array( 'CN_match' => $this->cnmatch ) ); // Enable validation of the SSL certificates. if ($this->verify_peer) { $default['ssl'] += array( 'verify_peer' => false, 'capath' => '', 'cafile' => '' ); $opts['ssl'] += array( 'verify_peer' => true, 'capath' => $this->capath, 'cafile' => $this->cainfo ); } // Change the stream context options. stream_context_get_default($opts); $headers = get_headers($url . ($params ? '?' . $params : '')); // Restore the stream context options. stream_context_get_default($default); if (!empty($headers)) { if (intval(substr($headers[0], strlen('HTTP/1.1 '))) == 405) { // The server doesn't support HEAD - emulate it with a GET. $args = func_get_args(); $args[1] = 'GET'; call_user_func_array(array($this, 'request_streams'), $args); $headers = $this->headers; } else { $headers = $this->parse_header_array($headers, $update_claimed_id); } } else { $headers = array(); } return $headers; } if ($this->verify_peer) { $opts['ssl'] += array( 'verify_peer' => true, 'capath' => $this->capath, 'cafile' => $this->cainfo ); } $context = stream_context_create($opts); $data = file_get_contents($url, false, $context); # This is a hack for providers who don't support HEAD requests. # It just creates the headers array for the last request in $this->headers. if (isset($http_response_header)) { $this->headers = $this->parse_header_array($http_response_header, $update_claimed_id); } return $data; } /** * @param $url * @param string $method * @param array $params * @param bool $update_claimed_id * * @return array|bool|false|string * @throws ErrorException */ protected function request($url, $method='GET', $params=array(), $update_claimed_id=false) { $use_curl = false; if (function_exists('curl_init')) { if (!$use_curl) { # When allow_url_fopen is disabled, PHP streams will not work. $use_curl = !ini_get('allow_url_fopen'); } if (!$use_curl) { # When there is no HTTPS wrapper, PHP streams cannott be used. $use_curl = !in_array('https', stream_get_wrappers()); } if (!$use_curl) { # With open_basedir or safe_mode set, cURL can't follow redirects. $use_curl = !(ini_get('safe_mode') || ini_get('open_basedir')); } } return $use_curl ? $this->request_curl($url, $method, $params, $update_claimed_id) : $this->request_streams($url, $method, $params, $update_claimed_id); } /** * @return string */ protected function proxy_url() { $result = ''; if (!empty($this->proxy)) { $result = $this->proxy['host']; if (!empty($this->proxy['port'])) { $result = $result . ':' . $this->proxy['port']; } if (!empty($this->proxy['user'])) { $result = $this->proxy['user'] . ':' . $this->proxy['pass'] . '@' . $result; } $result = 'http://' . $result; } return $result; } /** * @param $url * @param $parts * * @return string */ protected function build_url($url, $parts) { if (isset($url['query'], $parts['query'])) { $parts['query'] = $url['query'] . '&' . $parts['query']; } $url = $parts + $url; $url = $url['scheme'] . '://' . (empty($url['username'])?'' :(empty($url['password'])? "{$url['username']}@" :"{$url['username']}:{$url['password']}@")) . $url['host'] . (empty($url['port'])?'':":{$url['port']}") . (empty($url['path'])?'':$url['path']) . (empty($url['query'])?'':"?{$url['query']}") . (empty($url['fragment'])?'':"#{$url['fragment']}"); return $url; } /** * Helper function used to scan for / tags and extract information * from them * * @param $content * @param $tag * @param $attrName * @param $attrValue * @param $valueName * * @return bool */ protected function htmlTag($content, $tag, $attrName, $attrValue, $valueName) { preg_match_all("#<{$tag}[^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*$valueName=['\"](.+?)['\"][^>]*/?>#i", $content, $matches1); preg_match_all("#<{$tag}[^>]*$valueName=['\"](.+?)['\"][^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*/?>#i", $content, $matches2); $result = array_merge($matches1[1], $matches2[1]); return empty($result)?false:$result[0]; } /** * Performs Yadis and HTML discovery. Normally not used. * @param $url Identity URL. * @return String OP Endpoint (i.e. OpenID provider address). * @throws ErrorException */ public function discover($url) { if (!$url) { throw new ErrorException('No identity supplied.'); } # Use xri.net proxy to resolve i-name identities if (!preg_match('#^https?:#', $url)) { $url = "https://xri.net/$url"; } # We save the original url in case of Yadis discovery failure. # It can happen when we'll be lead to an XRDS document # which does not have any OpenID2 services. $originalUrl = $url; # A flag to disable yadis discovery in case of failure in headers. $yadis = true; # Allows optional regex replacement of the URL, e.g. to use Google Apps # as an OpenID provider without setting up XRDS on the domain hosting. if (!is_null($this->xrds_override_pattern) && !is_null($this->xrds_override_replacement)) { $url = preg_replace($this->xrds_override_pattern, $this->xrds_override_replacement, $url); } # We'll jump a maximum of 5 times, to avoid endless redirections. for ($i = 0; $i < 5; $i ++) { if ($yadis) { $headers = $this->request($url, 'HEAD', array(), true); $next = false; if (isset($headers['x-xrds-location'])) { $url = $this->build_url(parse_url($url), parse_url(trim($headers['x-xrds-location']))); $next = true; } if (isset($headers['content-type']) && $this->is_allowed_type($headers['content-type'])) { # Found an XRDS document, now let's find the server, and optionally delegate. $content = $this->request($url, 'GET'); preg_match_all('#(.*?)#s', $content, $m); foreach ($m[1] as $content) { $content = ' ' . $content; # The space is added, so that strpos doesn't return 0. # OpenID 2 $ns = preg_quote('http://specs.openid.net/auth/2.0/', '#'); if (preg_match('#\s*'.$ns.'(server|signon)\s*#s', $content, $type)) { if ($type[1] == 'server') { $this->identifier_select = true; } preg_match('#(.*)#', $content, $server); preg_match('#<(Local|Canonical)ID>(.*)#', $content, $delegate); if (empty($server)) { return false; } # Does the server advertise support for either AX or SREG? $this->ax = (bool) strpos($content, 'http://openid.net/srv/ax/1.0'); $this->sreg = strpos($content, 'http://openid.net/sreg/1.0') || strpos($content, 'http://openid.net/extensions/sreg/1.1'); $server = $server[1]; if (isset($delegate[2])) { $this->identity = trim($delegate[2]); } $this->version = 2; $this->server = $server; return $server; } # OpenID 1.1 $ns = preg_quote('http://openid.net/signon/1.1', '#'); if (preg_match('#\s*'.$ns.'\s*#s', $content)) { preg_match('#(.*)#', $content, $server); preg_match('#<.*?Delegate>(.*)#', $content, $delegate); if (empty($server)) { return false; } # AX can be used only with OpenID 2.0, so checking only SREG $this->sreg = strpos($content, 'http://openid.net/sreg/1.0') || strpos($content, 'http://openid.net/extensions/sreg/1.1'); $server = $server[1]; if (isset($delegate[1])) { $this->identity = $delegate[1]; } $this->version = 1; $this->server = $server; return $server; } } $next = true; $yadis = false; $url = $originalUrl; $content = null; break; } if ($next) { continue; } # There are no relevant information in headers, so we search the body. $content = $this->request($url, 'GET', array(), true); if (isset($this->headers['x-xrds-location'])) { $url = $this->build_url(parse_url($url), parse_url(trim($this->headers['x-xrds-location']))); continue; } $location = $this->htmlTag($content, 'meta', 'http-equiv', 'X-XRDS-Location', 'content'); if ($location) { $url = $this->build_url(parse_url($url), parse_url($location)); continue; } } if (!$content) { $content = $this->request($url, 'GET'); } # At this point, the YADIS Discovery has failed, so we'll switch # to openid2 HTML discovery, then fallback to openid 1.1 discovery. $server = $this->htmlTag($content, 'link', 'rel', 'openid2.provider', 'href'); $delegate = $this->htmlTag($content, 'link', 'rel', 'openid2.local_id', 'href'); $this->version = 2; if (!$server) { # The same with openid 1.1 $server = $this->htmlTag($content, 'link', 'rel', 'openid.server', 'href'); $delegate = $this->htmlTag($content, 'link', 'rel', 'openid.delegate', 'href'); $this->version = 1; } if ($server) { # We found an OpenID2 OP Endpoint if ($delegate) { # We have also found an OP-Local ID. $this->identity = $delegate; } $this->server = $server; return $server; } throw new ErrorException("No OpenID Server found at $url", 404); } throw new ErrorException('Endless redirection!', 500); } /** * @param $content_type * * @return bool */ protected function is_allowed_type($content_type) { # Apparently, some providers return XRDS documents as text/html. # While it is against the spec, allowing this here shouldn't break # compatibility with anything. $allowed_types = array('application/xrds+xml', 'text/xml'); # Only allow text/html content type for the Yahoo logins, since # it might cause an endless redirection for the other providers. if ($this->get_provider_name($this->claimed_id) == 'yahoo') { $allowed_types[] = 'text/html'; } foreach ($allowed_types as $type) { if (strpos($content_type, $type) !== false) { return true; } } return false; } /** * @param $provider_url * * @return string */ protected function get_provider_name($provider_url) { $result = ''; if (!empty($provider_url)) { $tokens = array_reverse( explode('.', parse_url($provider_url, PHP_URL_HOST)) ); $result = strtolower( (count($tokens) > 1 && strlen($tokens[1]) > 3) ? $tokens[1] : (count($tokens) > 2 ? $tokens[2] : '') ); } return $result; } /** * @return array */ protected function sregParams() { $params = array(); # We always use SREG 1.1, even if the server is advertising only support for 1.0. # That's because it's fully backwards compatible with 1.0, and some providers # advertise 1.0 even if they accept only 1.1. One such provider is myopenid.com $params['openid.ns.sreg'] = 'http://openid.net/extensions/sreg/1.1'; if ($this->required) { $params['openid.sreg.required'] = array(); foreach ($this->required as $required) { if (!isset(self::$ax_to_sreg[$required])) { continue; } $params['openid.sreg.required'][] = self::$ax_to_sreg[$required]; } $params['openid.sreg.required'] = implode(',', $params['openid.sreg.required']); } if ($this->optional) { $params['openid.sreg.optional'] = array(); foreach ($this->optional as $optional) { if (!isset(self::$ax_to_sreg[$optional])) { continue; } $params['openid.sreg.optional'][] = self::$ax_to_sreg[$optional]; } $params['openid.sreg.optional'] = implode(',', $params['openid.sreg.optional']); } return $params; } /** * @return array */ protected function axParams() { $params = array(); if ($this->required || $this->optional) { $params['openid.ns.ax'] = 'http://openid.net/srv/ax/1.0'; $params['openid.ax.mode'] = 'fetch_request'; $this->aliases = array(); $counts = array(); $required = array(); $optional = array(); foreach (array('required','optional') as $type) { foreach ($this->$type as $alias => $field) { if (is_int($alias)) { $alias = strtr($field, '/', '_'); } $this->aliases[$alias] = 'http://axschema.org/' . $field; if (empty($counts[$alias])) { $counts[$alias] = 0; } $counts[$alias] += 1; ${$type}[] = $alias; } } foreach ($this->aliases as $alias => $ns) { $params['openid.ax.type.' . $alias] = $ns; } foreach ($counts as $alias => $count) { if ($count == 1) { continue; } $params['openid.ax.count.' . $alias] = $count; } # Don't send empty ax.required and ax.if_available. # Google and possibly other providers refuse to support ax when one of these is empty. if ($required) { $params['openid.ax.required'] = implode(',', $required); } if ($optional) { $params['openid.ax.if_available'] = implode(',', $optional); } } return $params; } /** * @param $immediate * * @return string */ protected function authUrl_v1($immediate) { $returnUrl = $this->returnUrl; # If we have an openid.delegate that is different from our claimed id, # we need to somehow preserve the claimed id between requests. # The simplest way is to just send it along with the return_to url. if ($this->identity != $this->claimed_id) { $returnUrl .= (strpos($returnUrl, '?') ? '&' : '?') . 'openid.claimed_id=' . $this->claimed_id; } $params = array( 'openid.return_to' => $returnUrl, 'openid.mode' => $immediate ? 'checkid_immediate' : 'checkid_setup', 'openid.identity' => $this->identity, 'openid.trust_root' => $this->trustRoot, ) + $this->sregParams(); return $this->build_url(parse_url($this->server), array('query' => http_build_query($params, '', '&'))); } /** * @param $immediate * * @return string */ protected function authUrl_v2($immediate) { $params = array( 'openid.ns' => 'http://specs.openid.net/auth/2.0', 'openid.mode' => $immediate ? 'checkid_immediate' : 'checkid_setup', 'openid.return_to' => $this->returnUrl, 'openid.realm' => $this->trustRoot, ); if ($this->ax) { $params += $this->axParams(); } if ($this->sreg) { $params += $this->sregParams(); } if (!$this->ax && !$this->sreg) { # If OP doesn't advertise either SREG, nor AX, let's send them both # in worst case we don't get anything in return. $params += $this->axParams() + $this->sregParams(); } if (!empty($this->oauth) && is_array($this->oauth)) { $params['openid.ns.oauth'] = 'http://specs.openid.net/extensions/oauth/1.0'; $params['openid.oauth.consumer'] = str_replace(array('http://', 'https://'), '', $this->trustRoot); $params['openid.oauth.scope'] = implode(' ', $this->oauth); } if ($this->identifier_select) { $params['openid.identity'] = $params['openid.claimed_id'] = 'http://specs.openid.net/auth/2.0/identifier_select'; } else { $params['openid.identity'] = $this->identity; $params['openid.claimed_id'] = $this->claimed_id; } return $this->build_url(parse_url($this->server), array('query' => http_build_query($params, '', '&'))); } /** * Returns authentication url. Usually, you want to redirect your user to it. * @param bool $immediate * @return String The authentication url. * @throws ErrorException */ public function authUrl($immediate = false) { if ($this->setup_url && !$immediate) { return $this->setup_url; } if (!$this->server) { $this->discover($this->identity); } if ($this->version == 2) { return $this->authUrl_v2($immediate); } return $this->authUrl_v1($immediate); } /** * Performs OpenID verification with the OP. * @return Bool Whether the verification was successful. * @throws ErrorException */ public function validate() { # If the request was using immediate mode, a failure may be reported # by presenting user_setup_url (for 1.1) or reporting # mode 'setup_needed' (for 2.0). Also catching all modes other than # id_res, in order to avoid throwing errors. if (isset($this->data['openid_user_setup_url'])) { $this->setup_url = $this->data['openid_user_setup_url']; return false; } if ($this->mode != 'id_res') { return false; } $this->claimed_id = isset($this->data['openid_claimed_id'])?$this->data['openid_claimed_id']:$this->data['openid_identity']; $params = array( 'openid.assoc_handle' => $this->data['openid_assoc_handle'], 'openid.signed' => $this->data['openid_signed'], 'openid.sig' => $this->data['openid_sig'], ); if (isset($this->data['openid_ns'])) { # We're dealing with an OpenID 2.0 server, so let's set an ns # Even though we should know location of the endpoint, # we still need to verify it by discovery, so $server is not set here $params['openid.ns'] = 'http://specs.openid.net/auth/2.0'; } elseif (isset($this->data['openid_claimed_id']) && $this->data['openid_claimed_id'] != $this->data['openid_identity'] ) { # If it's an OpenID 1 provider, and we've got claimed_id, # we have to append it to the returnUrl, like authUrl_v1 does. $this->returnUrl .= (strpos($this->returnUrl, '?') ? '&' : '?') . 'openid.claimed_id=' . $this->claimed_id; } if ($this->data['openid_return_to'] != $this->returnUrl) { # The return_to url must match the url of current request. # I'm assuming that no one will set the returnUrl to something that doesn't make sense. return false; } $server = $this->discover($this->claimed_id); foreach (explode(',', $this->data['openid_signed']) as $item) { $value = $this->data['openid_' . str_replace('.', '_', $item)]; $params['openid.' . $item] = $value; } $params['openid.mode'] = 'check_authentication'; $response = $this->request($server, 'POST', $params); return preg_match('/is_valid\s*:\s*true/i', $response); } /** * @return array */ protected function getAxAttributes() { $result = array(); if ($alias = $this->getNamespaceAlias('http://openid.net/srv/ax/1.0', 'ax')) { $prefix = 'openid_' . $alias; $length = strlen('http://axschema.org/'); foreach (explode(',', $this->data['openid_signed']) as $key) { $keyMatch = $alias . '.type.'; if (strncmp($key, $keyMatch, strlen($keyMatch)) !== 0) { continue; } $key = substr($key, strlen($keyMatch)); $idv = $prefix . '_value_' . $key; $idc = $prefix . '_count_' . $key; $key = substr($this->getItem($prefix . '_type_' . $key), $length); if (!empty($key)) { if (($count = intval($this->getItem($idc))) > 0) { $value = array(); for ($i = 1; $i <= $count; $i++) { $value[] = $this->getItem($idv . '_' . $i); } $value = ($count == 1) ? reset($value) : $value; } else { $value = $this->getItem($idv); } if (!is_null($value)) { $result[$key] = $value; } } } } else { // No alias for the AX schema has been found, // so there is no AX data in the OP's response. } return $result; } /** * @return array */ protected function getSregAttributes() { $attributes = array(); $sreg_to_ax = array_flip(self::$ax_to_sreg); if ($alias = $this->getNamespaceAlias('http://openid.net/extensions/sreg/1.1', 'sreg')) { foreach (explode(',', $this->data['openid_signed']) as $key) { $keyMatch = $alias . '.'; if (strncmp($key, $keyMatch, strlen($keyMatch)) !== 0) { continue; } $key = substr($key, strlen($keyMatch)); if (!isset($sreg_to_ax[$key])) { # The field name isn't part of the SREG spec, so we ignore it. continue; } $attributes[$sreg_to_ax[$key]] = $this->data['openid_' . $alias . '_' . $key]; } } return $attributes; } /** * Gets AX/SREG attributes provided by OP. should be used only after successful validation. * Note that it does not guarantee that any of the required/optional parameters will be present, * or that there will be no other attributes besides those specified. * In other words. OP may provide whatever information it wants to. * * SREG names will be mapped to AX names. * * * @return array Array of attributes with keys being the AX schema names, e.g. 'contact/email' @see http://www.axschema.org/types/ */ public function getAttributes() { if (isset($this->data['openid_ns']) && $this->data['openid_ns'] == 'http://specs.openid.net/auth/2.0' ) { # OpenID 2.0 # We search for both AX and SREG attributes, with AX taking precedence. return $this->getAxAttributes() + $this->getSregAttributes(); } return $this->getSregAttributes(); } /** * Gets an OAuth request token if the OpenID+OAuth hybrid protocol has been used. * * In order to use the OpenID+OAuth hybrid protocol, you need to add at least one * scope to the $openid->oauth array before you get the call to getAuthUrl(), e.g.: * $openid->oauth[] = 'https://www.googleapis.com/auth/plus.me'; * * Furthermore the registered consumer name must fit the OpenID realm. * To register an OpenID consumer at Google use: https://www.google.com/accounts/ManageDomains * * @return string|bool OAuth request token on success, FALSE if no token was provided. */ public function getOAuthRequestToken() { $alias = $this->getNamespaceAlias('http://specs.openid.net/extensions/oauth/1.0'); return !empty($alias) ? $this->data['openid_' . $alias . '_request_token'] : false; } /** * Gets the alias for the specified namespace, if it's present. * * @param string $namespace The namespace for which an alias is needed. * @param string $hint Common alias of this namespace, used for optimization. * @return string|null The namespace alias if found, otherwise - NULL. */ private function getNamespaceAlias($namespace, $hint = null) { $result = null; if (empty($hint) || $this->getItem('openid_ns_' . $hint) != $namespace) { // The common alias is either undefined or points to // some other extension - search for another alias.. $prefix = 'openid_ns_'; $length = strlen($prefix); foreach ($this->data as $key => $val) { if (strncmp($key, $prefix, $length) === 0 && $val === $namespace) { $result = trim(substr($key, $length)); break; } } } else { $result = $hint; } return $result; } /** * Gets an item from the $data array by the specified id. * * @param string $id The id of the desired item. * @return string|null The item if found, otherwise - NULL. */ private function getItem($id) { return isset($this->data[$id]) ? $this->data[$id] : null; } } lib/hybridauth/Thirdparty/readme.md000064400000001355147577535410013414 0ustar00##### Third party libraries Here we include a number of third party libraries. Those libraries are used by the various providers supported by Hybridauth. Library | Description -------- | ------------- [LightOpenID](https://gitorious.org/lightopenid) | Contain LightOpenID. Solid OpenID library licensed under the MIT License. [OAuth Library](https://code.google.com/p/oauth/) | Contain OAuth Library licensed under the MIT License. Notes: We no longer use the old OAuth clients. Please don't add new libs to this folder, unless strictly necessary. Both LightOpenID and OAuth are (to be) partially/indirectly tested within the Hybridauth library. Both LightOpenID and OAuth libraries are excluded from Codeclimate.com Analysis/GPA. lib/hybridauth/User/Profile.php000064400000006352147577535410012534 0ustar00user = new \stdClass(); // typically, we should have a few information about the user who created the event from social apis $this->user->identifier = null; $this->user->displayName = null; $this->user->profileURL = null; $this->user->photoURL = null; } /** * Prevent the providers adapters from adding new fields. * * @throws UnexpectedValueException * @var string $name * * @var mixed $value * */ public function __set($name, $value) { // phpcs:ignore throw new UnexpectedValueException(sprintf('Adding new property "%s\' to %s is not allowed.', $name, __CLASS__)); } } lib/hybridauth/Hybridauth.php000064400000016271147577535410012322 0ustar00config = $config + [ 'debug_mode' => Logger::NONE, 'debug_file' => '', 'curl_options' => null, 'providers' => [] ]; $this->storage = $storage; $this->logger = $logger; $this->httpClient = $httpClient; } /** * Instantiate the given provider and authentication or authorization protocol. * * If not authenticated yet, the user will be redirected to the provider's site for * authentication/authorisation, otherwise it will simply return an instance of * provider's adapter. * * @param string $name adapter's name (case insensitive) * * @return \Hybridauth\Adapter\AdapterInterface * @throws InvalidArgumentException * @throws UnexpectedValueException */ public function authenticate($name) { $adapter = $this->getAdapter($name); $adapter->authenticate(); return $adapter; } /** * Returns a new instance of a provider's adapter by name * * @param string $name adapter's name (case insensitive) * * @return \Hybridauth\Adapter\AdapterInterface * @throws InvalidArgumentException * @throws UnexpectedValueException */ public function getAdapter($name) { $config = $this->getProviderConfig($name); $adapter = isset($config['adapter']) ? $config['adapter'] : sprintf('Hybridauth\\Provider\\%s', $name); if (!class_exists($adapter)) { $adapter = null; $fs = new \FilesystemIterator(__DIR__ . '/Provider/'); /** @var \SplFileInfo $file */ foreach ($fs as $file) { if (!$file->isDir()) { $provider = strtok($file->getFilename(), '.'); if (strtolower($name) === mb_strtolower($provider)) { $adapter = sprintf('Hybridauth\\Provider\\%s', $provider); break; } } } if ($adapter === null) { throw new InvalidArgumentException('Unknown Provider.'); } } return new $adapter($config, $this->httpClient, $this->storage, $this->logger); } /** * Get provider config by name. * * @param string $name adapter's name (case insensitive) * * @throws UnexpectedValueException * @throws InvalidArgumentException * * @return array */ public function getProviderConfig($name) { $name = strtolower($name); $providersConfig = array_change_key_case($this->config['providers'], CASE_LOWER); if (!isset($providersConfig[$name])) { throw new InvalidArgumentException('Unknown Provider.'); } if (!$providersConfig[$name]['enabled']) { throw new UnexpectedValueException('Disabled Provider.'); } $config = $providersConfig[$name]; $config += [ 'debug_mode' => $this->config['debug_mode'], 'debug_file' => $this->config['debug_file'], ]; if (!isset($config['callback']) && isset($this->config['callback'])) { $config['callback'] = $this->config['callback']; } return $config; } /** * Returns a boolean of whether the user is connected with a provider * * @param string $name adapter's name (case insensitive) * * @return bool * @throws InvalidArgumentException * @throws UnexpectedValueException */ public function isConnectedWith($name) { return $this->getAdapter($name)->isConnected(); } /** * Returns a list of enabled adapters names * * @return array */ public function getProviders() { $providers = []; foreach ($this->config['providers'] as $name => $config) { if ($config['enabled']) { $providers[] = $name; } } return $providers; } /** * Returns a list of currently connected adapters names * * @return array * @throws InvalidArgumentException * @throws UnexpectedValueException */ public function getConnectedProviders() { $providers = []; foreach ($this->getProviders() as $name) { if ($this->isConnectedWith($name)) { $providers[] = $name; } } return $providers; } /** * Returns a list of new instances of currently connected adapters * * @return \Hybridauth\Adapter\AdapterInterface[] * @throws InvalidArgumentException * @throws UnexpectedValueException */ public function getConnectedAdapters() { $adapters = []; foreach ($this->getProviders() as $name) { $adapter = $this->getAdapter($name); if ($adapter->isConnected()) { $adapters[$name] = $adapter; } } return $adapters; } /** * Disconnect all currently connected adapters at once */ public function disconnectAllAdapters() { foreach ($this->getProviders() as $name) { $adapter = $this->getAdapter($name); if ($adapter->isConnected()) { $adapter->disconnect(); } } } } lib/hybridauth/autoload.php000064400000003676147577535410012034 0ustar00 $iv){ $ignores[$iv] = array(); if(!empty($files[$iv])){ $ignores[$iv] = $files[$iv]; } } $lz_env['files'] = $files; $lz_env['ignores'] = $ignores; } // Loginizer - PasswordLess Page function loginizer_page_checksums(){ global $loginizer, $lz_error, $lz_env; if(!current_user_can('manage_options')){ wp_die('Sorry, but you do not have permissions to change settings.'); } if(!loginizer_is_premium() && count($_POST) > 0){ $lz_error['not_in_free'] = __('This feature is not available in the Free version.
Upgrade to Pro', 'loginizer'); return loginizer_page_checksums_T(); } /* Make sure post was from this page */ if(count($_POST) > 0){ check_admin_referer('loginizer-options'); } // Are we to run it ? if(isset($_REQUEST['lz_run_checksum'])){ loginizer_checksums(); } loginizer_page_checksums_L($files, $_ignores); $lz_env['csum_freq'][1] = __('Once a Day', 'loginizer'); $lz_env['csum_freq'][7] = __('Once a Week', 'loginizer'); $lz_env['csum_freq'][30] = __('Once a Month', 'loginizer'); if(isset($_POST['save_lz'])){ // In the future there can be more settings $option['disable_checksum'] = (int) lz_optpost('disable_checksum'); $option['no_checksum_email'] = (int) lz_optpost('no_checksum_email'); $option['checksum_frequency'] = (int) lz_optpost('checksum_frequency'); $option['checksum_time'] = lz_optpost('checksum_time'); // Is there an error ? if(!empty($lz_error)){ return loginizer_page_checksums_T(); } // Save the options update_option('loginizer_checksums', $option); // Mark as saved $GLOBALS['lz_saved'] = true; } // Add or remove from ignore list if(isset($_POST['save_lz_csum_ig'])){ if(@is_array($_POST['checksum_del_ignore'])){ foreach($_POST['checksum_del_ignore'] as $k => $v){ $key = array_search($v, $_ignores); if($key !== false){ unset($_ignores[$key]); } } // Save it update_option('loginizer_checksums_ignore', $_ignores); } if(@is_array($_POST['checksum_add_ignore'])){ foreach($_POST['checksum_add_ignore'] as $k => $v){ if(!empty($files[$v])){ $_ignores[] = $v; } } // Save it update_option('loginizer_checksums_ignore', $_ignores); } // Reload loginizer_page_checksums_L($files, $_ignores); // Mark as saved $GLOBALS['lz_saved'] = true; } // Call theme loginizer_page_checksums_T(); } // Loginizer - PasswordLess Page Theme function loginizer_page_checksums_T(){ global $loginizer, $lz_error, $lz_env; // Universal header loginizer_page_header('File Checksum Settings'); loginizer_feature_available('File Checksum'); if(defined('LOGINIZER_PRO_DIR_URL')){ wp_enqueue_script('jquery-clockpicker', LOGINIZER_PRO_DIR_URL.'/assets/js/jquery-clockpicker.min.js', array('jquery'), '0.0.7'); wp_enqueue_style('jquery-clockpicker', LOGINIZER_PRO_DIR_URL.'/assets/css/jquery-clockpicker.min.css', array(), '0.0.7'); } // Saved ? if(!empty($GLOBALS['lz_saved'])){ echo '

'. __('The settings were saved successfully', 'loginizer'). '


'; } // Did we just run the checksums if(isset($_REQUEST['lz_run_checksum'])){ echo '

'. __('The Checksum process was executed successfully', 'loginizer'). '


'; } // Any errors ? if(!empty($lz_error)){ lz_report_error($lz_error);echo '
'; } ?>


/>

/>



$v){ if(!empty($lz_env['ignores'][$k])){ unset($files[$k]); } } } echo ' '; if(is_array($files) && count($files) > 0){ foreach($files as $k => $v){ echo ' '; } }else{ echo ' '; } ?>
'.__('Relative Path', 'loginizer').' '.__('Found', 'loginizer').' '.__('Should be', 'loginizer').'
'.$k.' '.$v['cur_md5'].' '.$v['md5'].'
'.__('This is great ! No file with any wrong checksum has been found.','loginizer').'


'; // Load any mismatched files $files = $ignores; if(is_array($files) && count($files) > 0){ foreach($files as $k => $v){ echo ' '; } }else{ echo ' '; } ?>
'.__('Relative Path', 'loginizer').' '.__('Found', 'loginizer').' '.__('Should be', 'loginizer').'
'.$k.' '.$v['cur_md5'].' '.$v['md5'].'
'.__('No files have been added to the ignore list','loginizer').'


0){ $lz_error['not_in_free'] = __('This feature is not available in the Free version. Upgrade to Pro', 'loginizer'); return loginizer_page_security_T(); } /* Make sure post was from this page */ if(count($_POST) > 0){ check_admin_referer('loginizer-options'); } if(isset($_POST['save_lz'])){ $option['login_slug'] = lz_optpost('login_slug'); $option['rename_login_secret'] = (int) lz_optpost('rename_login_secret'); $option['xmlrpc_slug'] = lz_optpost('xmlrpc_slug'); $option['xmlrpc_disable'] = (int) lz_optpost('xmlrpc_disable'); $option['pingbacks_disable'] = (int) lz_optpost('pingbacks_disable'); // Login Slug Valid ? if(!empty($option['login_slug'])){ if(strlen($option['login_slug']) <= 4 || strlen($option['login_slug']) > 50){ $lz_error['login_slug'] = __('The Login slug length must be greater than 4 chars and upto 50 chars long', 'loginizer'); } } // login slug and admin slug cannot be the same $_loginizer_wp_admin = get_option('loginizer_wp_admin'); if(!empty($_loginizer_wp_admin['admin_slug']) && $_loginizer_wp_admin['admin_slug'] == $option['login_slug']){ $lz_error['lz_same_slug'] = __('The wp-login.php and wp-admin slugs cannot be the same. Choose unique names for login and admin slugs', 'loginizer'); return loginizer_page_security_T(); } // XML-RPC Slug Valid ? if(!empty($option['xmlrpc_slug'])){ if(strlen($option['xmlrpc_slug']) <= 4 || strlen($option['xmlrpc_slug']) > 50){ $lz_error['xmlrpc_slug'] = __('The XML-RPC slug length must be greater than 4 chars and upto 50 chars long', 'loginizer'); } } // Is there an error ? if(!empty($lz_error)){ return loginizer_page_security_T(); } // Save the options update_option('loginizer_security', $option); // Mark as saved $GLOBALS['lz_saved'] = true; } // Reset the username if(isset($_POST['save_lz_admin'])){ // Get the new username $current_username = lz_optpost('current_username'); $new_username = lz_optpost('new_username'); if(empty($current_username)){ $lz_error['current_username_empty'] = __('Current username is required', 'loginizer'); return loginizer_page_security_T(); } if(empty($new_username)){ $lz_error['new_username_empty'] = __('New username is required', 'loginizer'); return loginizer_page_security_T(); } // Is the starting of the username having 'admin' ? if(@strtolower(substr($new_username, 0, 5)) == 'admin'){ $lz_error['user_exists'] = __('The username begins with admin. Please change it !', 'loginizer'); return loginizer_page_security_T(); } // Lets check if there is such a user $found = get_user_by('login', $new_username); // Found one ! if(!empty($found->ID)){ $lz_error['user_exists'] = __('The new username is already assigned to another user', 'loginizer'); return loginizer_page_security_T(); } $old_user = get_user_by('login', $current_username); if(empty($old_user->ID)){ $lz_error['current_username_invalid'] = __('No user found with the current username provided', 'loginizer'); return loginizer_page_security_T(); } if(empty($old_user->caps['administrator'])){ $lz_error['user_not_admin'] = __('The user is not an administrator. Only administrator user\'s username can be changed.', 'loginizer'); return loginizer_page_security_T(); } $is_super_admin = 0; if(is_multisite() && is_super_admin($old_user->ID)){ $is_super_admin = 1; } // Update the username $update_data = array('user_login' => $new_username); $where_data = array('ID' => $old_user->ID); $format = array('%s'); $where_format = array('%d'); $wpdb->update($wpdb->prefix.'users', $update_data, $where_data, $format, $where_format); // Update the super admins list for multisite if(!empty($is_super_admin)){ $super_admins = get_site_option('site_admins'); foreach($super_admins as $sk => $sv){ // Remove the existing username from super admins list if($sv == $current_username){ unset($super_admins[$sk]); } } // Add the new username $super_admins[] = $new_username; update_site_option( 'site_admins', $super_admins ); } // Mark as saved $GLOBALS['lz_saved'] = true; } // Change the wp-admin slug if(isset($_POST['save_lz_wp_admin'])){ // Get the new username $option['admin_slug'] = lz_optpost('admin_slug'); $option['restrict_wp_admin'] = (int) lz_optpost('restrict_wp_admin'); $option['wp_admin_msg'] = @stripslashes($_POST['wp_admin_msg']); $lz_wp_admin_docs = (int) lz_optpost('lz_wp_admin_docs'); // login slug and admin slug cannot be the same $_loginizer_security = get_option('loginizer_security'); if(!empty($_loginizer_security['login_slug']) && $_loginizer_security['login_slug'] == $option['admin_slug']){ $lz_error['lz_same_slug'] = __('The wp-login.php and wp-admin slugs cannot be the same. Choose unique names for login and admin slugs', 'loginizer'); return loginizer_page_security_T(); } // Did you agree to this ? if(!empty($option['admin_slug']) && empty($lz_wp_admin_docs)){ $lz_error['lz_wp_admin_docs'] = __('You have not confirmed that you have read the guide and configured .htaccess. Please read the guide, configure .htaccess and then save these settings and check this checkbox', 'loginizer'); return loginizer_page_security_T(); } // Length if(!empty($option['admin_slug']) && (strlen($option['admin_slug']) <= 4 || strlen($option['admin_slug']) > 50)){ $lz_error['admin_slug'] = __('The new Admin slug length must be greater than 4 chars and upto 50 chars long', 'loginizer'); return loginizer_page_security_T(); } // Only regular characters if(preg_match('/[^\w\d\-_]/is', $option['admin_slug'])){ $lz_error['admin_slug_chars'] = __('Special characters are not allowed', 'loginizer'); return loginizer_page_security_T(); } // Update the option update_option('loginizer_wp_admin', $option); // Mark as saved $GLOBALS['lz_saved'] = true; } // Save blacklisted usernames if(isset($_POST['save_lz_bl_users'])){ $usernames = isset($_POST['lz_bl_users']) && is_array($_POST['lz_bl_users']) ? $_POST['lz_bl_users'] : array(); // Process the usernames i.e. remove blanks foreach($usernames as $k => $v){ $v = trim($v); // Unset blank values if(empty($v)){ unset($usernames[$k]); } // Disallow these special characters to avoid XSS or any other security vulnerability if(preg_match('/[\<\>\"\']/', $v)){ unset($usernames[$k]); } } // Update the blacklist update_option('loginizer_username_blacklist', array_values($usernames)); // Mark as saved $GLOBALS['lz_saved'] = true; } // Save blacklisted domains if(isset($_POST['save_lz_bl_domains'])){ $domains = isset($_POST['lz_bl_domains']) && is_array($_POST['lz_bl_domains']) ? $_POST['lz_bl_domains'] : array(); // Process the domains i.e. remove blanks foreach($domains as $k => $v){ $v = trim($v); // Unset blank values if(empty($v)){ unset($domains[$k]); } // Disallow these special characters to avoid XSS or any other security vulnerability if(preg_match('/[\<\>\"\']/', $v)){ unset($domains[$k]); } } // Update the blacklist update_option('loginizer_domains_blacklist', array_values($domains)); // Mark as saved $GLOBALS['lz_saved'] = true; } if(isset($_POST['save_lz_csrf_protection'])){ update_option('loginizer_csrf_protection', empty(lz_optpost('enable_csrf_protection')) ? false : true); delete_transient('loginizer_csrf_mod_rewrite'); $GLOBALS['lz_saved'] = true; } if(isset($_POST['save_lz_limit_session'])){ $limit_session = map_deep($_POST['limit_session'], 'sanitize_text_field'); if(empty($limit_session)){ delete_option('loginizer_limit_session'); } else { update_option('loginizer_limit_session', $limit_session); } $GLOBALS['lz_saved'] = true; } // Call theme loginizer_page_security_T(); } // Loginizer - Security Settings Page Theme function loginizer_page_security_T(){ global $loginizer, $lz_error, $lz_env; // Universal header loginizer_page_header('Security Settings'); loginizer_feature_available('Security Settings'); // Saved ? if(!empty($GLOBALS['lz_saved'])){ echo '

'. __('The settings were saved successfully', 'loginizer'). '


'; } // Any errors ? if(!empty($lz_error)){ lz_report_error($lz_error);echo '
'; } $current_admin = get_user_by('id', 1); ?>

'. $loginizer['login_basename'].' '.__(' to anything of your choice e.g. mylogin. This would make it very difficult for automated attack bots to know where to login !','loginizer'); ?>


/>

disable the XML-RPC feature as it prevents attackers from using the feature to attack the site. If your service can use a custom XML-RPC URL, you can also rename the XML-RPC page to a custom slug.', 'loginizer'); ?>
/>
/>


'; } if(defined('LOGINIZER_PREMIUM') && !empty($loginizer['enable_csrf_protection']) && empty($loginizer['admin_slug'])){ echo '
'.esc_html__('Note: Be careful while changing the Admin name as your CSRF Protection is on', 'loginizer').'
'; } ?>
'.__('Rename wp-admin access feature is supported only on Apache and Litespeed', 'loginizer').'
wp-admin to anything of your choice e.g. my-admin. This will require you to change .htaccess, so please follow','loginizer'); ?> '.__('on how to do so !','loginizer'); ?>


', 'loginizer'); ?>
/>

our guide so that we can safely enable this feature', 'loginizer'); ?>


/>




admin, administrator, or variations of your domain name / business name. You can specify such username here and Loginizer will auto-blacklist the IP Address(s) of clients who try to use such username(s).', 'loginizer'); ?>

* (Star)- as a wild card as well. Blank fields will be ignored', 'loginizer'); ?>
\"\']/', $_user)){ continue; } echo ''; } ?>


If you would like to ban new registrations from a particular domain, you can use this utility to do so.

* (Star)- as a wild card as well. Blank fields will be ignored', 'loginizer'); ?>
\"\']/', $_domain)){ continue; } echo ''; } ?>


New'; } ?>


/>

/> '.__('Block', 'loginizer') . ' : ' . __('Blocks all the login attempts if limit is reached', 'loginizer'); ?>
/> '.__('Destroy', 'loginizer') . ' : ' . __('Revokes all the sessions on successful login', 'loginizer'); ?>


roles as $key => $role){ $checked = ''; if(!empty($_POST['limit_session']['roles']) && in_array($key, $_POST['limit_session']['roles']) || !empty($loginizer['limit_session']['roles']) && in_array($key, $loginizer['limit_session']['roles'])){ $checked = 'checked'; } echo ''. esc_html($role['name']) . '
'; } ?>

' . "\n"; $rule .= 'RewriteEngine On' . "\n"; $rule .= 'RewriteBase ' . $home_root . "\n\n"; $rule .= 'RewriteRule ^' . $admin_slug . '(-lzs.{20})?(/?)(.*) wp-admin/$3 [L]' . "\n"; $rule .= '' . "\n"; $rule .= '# END Loginizer' . "\n"; if(is_writable(ABSPATH . '/.htaccess')){ echo '
'. (!empty($is_csrf) ? esc_html__('Rewrites rule for CSRF session URL', 'loginizer') : esc_html__('Rewrites rule to change wp-admin and if you have a Multisite then check', 'loginizer') . ' our guide') . ' Show Rewrite Rule

'; } else { echo '
' . esc_html__('You can manually update your .htaccess by adding the given code at the top of your .htaccess file', 'loginizer'). ' '; } }main/settings/passwordless.php000064400000017412147577535410012621 0ustar00 0){ $lz_error['not_in_free'] = __('This feature is not available in the Free version. Upgrade to Pro', 'loginizer'); return loginizer_page_passwordless_T(); } /* Make sure post was from this page */ if(count($_POST) > 0){ check_admin_referer('loginizer-options'); } if(isset($_POST['save_lz'])){ // In the future there can be more settings $option['email_pass_less'] = (int) lz_optpost('email_pass_less'); $option['passwordless_sub'] = @stripslashes($_POST['lz_passwordless_sub']); $option['passwordless_msg'] = @stripslashes($_POST['lz_passwordless_msg']); $option['passwordless_html'] = (int) lz_optpost('lz_passwordless_html'); $option['passwordless_redirect'] = esc_url_raw($_POST['lz_passwordless_redirect']); $option['passwordless_redirect_for'] = !empty($_POST['lz_passwordless_redirect_for']) ? map_deep($_POST['lz_passwordless_redirect_for'], 'sanitize_text_field') : []; // Is there an error ? if(!empty($lz_error)){ return loginizer_page_passwordless_T(); } // Save the options update_option('loginizer_epl', $option); // Mark as saved $GLOBALS['lz_saved'] = true; } // Call theme loginizer_page_passwordless_T(); } // Loginizer - PasswordLess Page Theme function loginizer_page_passwordless_T(){ global $loginizer, $lz_error, $lz_env; $lz_options = get_option('loginizer_epl'); // Universal header loginizer_page_header('PasswordLess Settings'); loginizer_feature_available('PasswordLess Login'); // Saved ? if(!empty($GLOBALS['lz_saved'])){ echo '

'. __('The settings were saved successfully', 'loginizer'). '


'; } // Any errors ? if(!empty($lz_error)){ lz_report_error($lz_error);echo '
'; } ?>

/>
OR email address of the user. If such a user exists, an email with a One Time Login link will be sent to the email address of the user. The link will be valid for 10 minutes only.', 'loginizer'); ?>



Default :


Default :

Variables :
$email - Users Email
$site_name - The Site Name
$site_url - The Site URL
$login_url - The Login URL
/>


'; $r = ''; foreach($editable_roles as $role => $details) { $name = translate_user_role( $details['name'] ); // Preselect specified role. if(!empty($lz_options['passwordless_redirect_for']) && in_array($role, $lz_options['passwordless_redirect_for'])) { $r .= "\n\t$name"; } else { $r .= "\n\t$name"; } $r .= '
'; } echo $r . ''; ?>


0){ $lz_error['not_in_free'] = __('This feature is not available in the Free version. Upgrade to Pro', 'loginizer'); return loginizer_page_recaptcha_T(); } /* Make sure post was from this page */ if(count($_POST) > 0){ check_admin_referer('loginizer-options'); } // Themes $lz_env['theme']['light'] = 'Light'; $lz_env['theme']['dark'] = 'Dark'; // Langs $lz_env['lang'][''] = 'Auto Detect'; $lz_env['lang']['ar'] = 'Arabic'; $lz_env['lang']['bg'] = 'Bulgarian'; $lz_env['lang']['ca'] = 'Catalan'; $lz_env['lang']['zh-CN'] = 'Chinese (Simplified)'; $lz_env['lang']['zh-TW'] = 'Chinese (Traditional)'; $lz_env['lang']['hr'] = 'Croatian'; $lz_env['lang']['cs'] = 'Czech'; $lz_env['lang']['da'] = 'Danish'; $lz_env['lang']['nl'] = 'Dutch'; $lz_env['lang']['en-GB'] = 'English (UK)'; $lz_env['lang']['en'] = 'English (US)'; $lz_env['lang']['fil'] = 'Filipino'; $lz_env['lang']['fi'] = 'Finnish'; $lz_env['lang']['fr'] = 'French'; $lz_env['lang']['fr-CA'] = 'French (Canadian)'; $lz_env['lang']['de'] = 'German'; $lz_env['lang']['de-AT'] = 'German (Austria)'; $lz_env['lang']['de-CH'] = 'German (Switzerland)'; $lz_env['lang']['el'] = 'Greek'; $lz_env['lang']['iw'] = 'Hebrew'; $lz_env['lang']['hi'] = 'Hindi'; $lz_env['lang']['hu'] = 'Hungarain'; $lz_env['lang']['id'] = 'Indonesian'; $lz_env['lang']['it'] = 'Italian'; $lz_env['lang']['ja'] = 'Japanese'; $lz_env['lang']['ko'] = 'Korean'; $lz_env['lang']['lv'] = 'Latvian'; $lz_env['lang']['lt'] = 'Lithuanian'; $lz_env['lang']['no'] = 'Norwegian'; $lz_env['lang']['fa'] = 'Persian'; $lz_env['lang']['pl'] = 'Polish'; $lz_env['lang']['pt'] = 'Portuguese'; $lz_env['lang']['pt-BR'] = 'Portuguese (Brazil)'; $lz_env['lang']['pt-PT'] = 'Portuguese (Portugal)'; $lz_env['lang']['ro'] = 'Romanian'; $lz_env['lang']['ru'] = 'Russian'; $lz_env['lang']['sr'] = 'Serbian'; $lz_env['lang']['sk'] = 'Slovak'; $lz_env['lang']['sl'] = 'Slovenian'; $lz_env['lang']['es'] = 'Spanish'; $lz_env['lang']['es-419'] = 'Spanish (Latin America)'; $lz_env['lang']['sv'] = 'Swedish'; $lz_env['lang']['th'] = 'Thai'; $lz_env['lang']['tr'] = 'Turkish'; $lz_env['lang']['uk'] = 'Ukrainian'; $lz_env['lang']['vi'] = 'Vietnamese'; // Sizes $lz_env['size']['normal'] = 'Normal'; $lz_env['size']['compact'] = 'Compact'; // reCAPTCHA Domains $lz_env['captcha_domains']['www.google.com'] = 'google.com'; $lz_env['captcha_domains']['www.recaptcha.net'] = 'recaptcha.net'; if(isset($_POST['save_lz'])){ // Clear captcha if(empty($_POST['captcha_status'])){ // Save the options update_option('loginizer_captcha', ''); // Mark as saved $GLOBALS['lz_cleared'] = true; }else{ $option['captcha_status'] = (int) lz_optpost('captcha_status'); //hcaptcha $option['hcaptcha_secretkey'] = lz_optpost('hcaptcha_secretkey'); $option['hcaptcha_sitekey'] = lz_optpost('hcaptcha_sitekey'); $option['hcaptcha_lang'] = lz_optpost('hcaptcha_lang'); $option['hcaptcha_theme'] = lz_optpost('hcaptcha_theme'); $option['hcaptcha_size'] = lz_optpost('hcaptcha_size'); // Google Captcha $option['captcha_type'] = lz_optpost('captcha_type'); $option['captcha_key'] = lz_optpost('captcha_key'); $option['captcha_secret'] = lz_optpost('captcha_secret'); $option['captcha_theme'] = lz_optpost('captcha_theme'); $option['captcha_size'] = lz_optpost('captcha_size'); $option['captcha_lang'] = lz_optpost('captcha_lang'); $option['captcha_domain'] = lz_optpost('captcha_domain'); // Cloudflare Turnstil Captcha $option['turn_captcha_key'] = lz_optpost('turn_captcha_key'); $option['turn_captcha_secret'] = lz_optpost('turn_captcha_secret'); $option['turn_captcha_theme'] = lz_optpost('turn_captcha_theme'); $option['turn_captcha_size'] = lz_optpost('turn_captcha_size'); $option['turn_captcha_lang'] = lz_optpost('turn_captcha_lang'); // No Google Captcha $option['captcha_text'] = lz_optpost('captcha_text'); $option['captcha_time'] = (int) lz_optpost('captcha_time'); $option['captcha_words'] = (int) lz_optpost('captcha_words'); $option['captcha_add'] = (int) lz_optpost('captcha_add'); $option['captcha_subtract'] = (int) lz_optpost('captcha_subtract'); $option['captcha_multiply'] = (int) lz_optpost('captcha_multiply'); $option['captcha_divide'] = (int) lz_optpost('captcha_divide'); // Checkboxes $option['captcha_user_hide'] = (int) lz_optpost('captcha_user_hide'); $option['captcha_login'] = (int) lz_optpost('captcha_login'); $option['captcha_lostpass'] = (int) lz_optpost('captcha_lostpass'); $option['captcha_resetpass'] = (int) lz_optpost('captcha_resetpass'); $option['captcha_register'] = (int) lz_optpost('captcha_register'); $option['captcha_comment'] = (int) lz_optpost('captcha_comment'); $option['captcha_wc_checkout'] = (int) lz_optpost('captcha_wc_checkout'); // Are we to use Math Captcha ? if(!empty($_POST['captcha_status']) && $_POST['captcha_status'] == 2){ $option['captcha_no_google'] = 1; // Make the checks if(strlen($option['captcha_text']) < 1){ $lz_error['captcha_text'] = __('The Captcha key was not submitted', 'loginizer'); } }else if(!empty($_POST['captcha_status']) && $_POST['captcha_status'] == 3){ if(strlen($option['hcaptcha_sitekey']) < 32 || strlen($option['hcaptcha_sitekey']) > 50){ $lz_error['hcaptcha_sitekey'] = __('The hCAPTCHA key is invalid', 'loginizer'); } // Is secret valid ? if(strlen($option['hcaptcha_secretkey']) < 32 || strlen($option['hcaptcha_secretkey']) > 50){ $lz_error['hcaptcha_secretkey'] = __('The hCAPTCHA secret is invalid', 'loginizer'); } // Is theme valid ? if(empty($lz_env['theme'][$option['hcaptcha_theme']])){ $lz_error['hcaptcha_theme'] = __('The hCaptcha theme is invalid', 'loginizer'); } // Is size valid ? if(empty($lz_env['size'][$option['hcaptcha_size']])){ $lz_error['hcaptcha_size'] = __('The Turnstile size is invalid', 'loginizer'); } // Is lang valid ? if(empty($lz_env['lang'][$option['turn_captcha_lang']])){ $lz_error['turn_captcha_lang'] = __('The Turnstile language is invalid', 'loginizer'); } }else if(!empty($_POST['captcha_status']) && $_POST['captcha_status'] == 4){ if(strlen($option['turn_captcha_key']) < 24 || strlen($option['turn_captcha_key']) > 50){ $lz_error['turn_captcha_key'] = __('The Turnstile Site key is invalid', 'loginizer'); } // Is secret valid ? if(strlen($option['turn_captcha_secret']) < 32 || strlen($option['turn_captcha_secret']) > 50){ $lz_error['turn_captcha_secret'] = __('The Turnstile secret key is invalid', 'loginizer'); } // Is theme valid ? if(empty($lz_env['theme'][$option['turn_captcha_theme']])){ $lz_error['turn_captcha_theme'] = __('The Turnstile theme is invalid', 'loginizer'); } // Is size valid ? if(empty($lz_env['size'][$option['turn_captcha_size']])){ $lz_error['turn_captcha_size'] = __('The Turnstile size is invalid', 'loginizer'); } // Is lang valid ? if(empty($lz_env['lang'][$option['turn_captcha_lang']])){ $lz_error['turn_captcha_lang'] = __('The Turnstile language is invalid', 'loginizer'); } }else{ // Make the checks if(strlen($option['captcha_key']) < 32 || strlen($option['captcha_key']) > 50){ $lz_error['captcha_key'] = __('The reCAPTCHA key is invalid', 'loginizer'); } // Is secret valid ? if(strlen($option['captcha_secret']) < 32 || strlen($option['captcha_secret']) > 50){ $lz_error['captcha_secret'] = __('The reCAPTCHA secret is invalid', 'loginizer'); } // Is theme valid ? if(empty($lz_env['theme'][$option['captcha_theme']])){ $lz_error['captcha_theme'] = __('The reCAPTCHA theme is invalid', 'loginizer'); } // Is size valid ? if(empty($lz_env['size'][$option['captcha_size']])){ $lz_error['captcha_size'] = __('The reCAPTCHA size is invalid', 'loginizer'); } // Is lang valid ? if(empty($lz_env['lang'][$option['captcha_lang']])){ $lz_error['captcha_lang'] = __('The reCAPTCHA language is invalid', 'loginizer'); } if(empty($lz_env['captcha_domains'][$option['captcha_domain']])){ $lz_error['captcha_domain'] = __('The reCAPTCHA domain is invalid', 'loginizer'); } } // Is there an error ? if(!empty($lz_error)){ return loginizer_page_recaptcha_T(); } // Save the options update_option('loginizer_captcha', $option); // Mark as saved $GLOBALS['lz_saved'] = true; } } // Call the theme loginizer_page_recaptcha_T(); } // Loginizer - reCaptcha Page Theme function loginizer_page_recaptcha_T(){ global $loginizer, $lz_error, $lz_env; // Universal header loginizer_page_header('reCAPTCHA Settings'); loginizer_feature_available('reCAPTCHA'); // Saved ? if(!empty($GLOBALS['lz_saved'])){ echo '

'. __('The settings were saved successfully', 'loginizer'). '


'; } // Cleared ? if(!empty($GLOBALS['lz_cleared'])){ echo '

'. __('reCAPTCHA has been disabled !', 'loginizer'). '


'; } // Any errors ? if(!empty($lz_error)){ lz_report_error($lz_error);echo '
'; } ?>



hcaptcha', 'loginizer'); ?>


See Site Types for more details', 'loginizer'); ?>
name="captcha_type" id="captcha_type_v3" />

name="captcha_type" id="captcha_type_v2" />

name="captcha_type" id="captcha_type_v2_invisible" />


Google', 'loginizer'); ?>



Cloudflare Turnstile', 'loginizer'); ?>



/>

'; ?>
'; if(!defined('SITEPAD')){ echo ''; } ?>
/>


wp_create_nonce('loginizer_social_nonce'), 'ajax_url' => admin_url('admin-ajax.php') ]; wp_localize_script('loginizer_social_script', 'loginizer_social', $script_data); loginizer_page_header('Social Login Settings'); echo ''; // Handling Settings Page if(!empty($_GET['settings'])){ loginizer_general_settings(); return; } // Contains the list of all the providers include_once LOGINIZER_DIR . '/main/login-providers.php'; if(empty($loginizer_login_providers)){ echo '

' . esc_html__('Something went wrong can\'t load social App details', 'loginizer') . '

'; return; } // Handling Provider settings. if(!empty($_GET['provider'])){ $provider_page = sanitize_text_field($_GET['provider']); loginizer_provider_settings($provider_page, $loginizer_login_providers); return; } $provider_settings = get_option('loginizer_provider_settings', []); $provider_order = get_option('loginizer_social_order', []); $providers = []; // Sorting according to the saved order of the providers. if(!empty($provider_order)){ foreach($provider_order as $key => $num){ if(!empty($loginizer_login_providers[$key])){ $providers[$key] = $loginizer_login_providers[$key]; } } // TODO:: There is some issue here. // To check if something new was added and will be added at the last. $remaining = array_diff(array_keys($loginizer_login_providers), array_keys($providers)); if(!empty($remaining)){ foreach($remaining as $new_social){ $providers[$new_social] = $loginizer_login_providers[$new_social]; } } } else { $providers = $loginizer_login_providers; } // ------ HTML STARTS HERE -------- // echo '
'; // Image used here is supposed to be same as the key in png format. foreach($providers as $key => $provider){ $action_class = ''; $action_text = __('Get Started'); if(array_key_exists($key, $provider_settings)){ $action_text = __('Settings'); } $action_to = admin_url('admin.php?page=loginizer_social_login&provider='.$key); if(!empty($provider['premium']) && !defined('LOGINIZER_PREMIUM')){ $action_text = __('Upgrade to Pro'); $action_class = ' button-primary'; // Have added space here at the start of the string $action_to = 'https://loginizer.com/pricing'; } echo '

'.esc_html($provider['name']).'

'.esc_html($action_text).'
'; } echo '
'; loginizer_page_footer(); } // Settings page for every Social Provider. function loginizer_provider_settings($provider, $provider_defaults){ $provider_settings = get_option('loginizer_provider_settings', []); if(empty($provider_settings)){ update_option('loginizer_social_login_url', wp_login_url()); } if(!empty($_GET['test'])){ $provider_settings[$provider]['tested'] = true; update_option('loginizer_provider_settings', $provider_settings); } // Checking for the Pro version for the specific Provider if(!defined('LOGINIZER_PREMIUM') && (empty($provider_defaults[$provider]) || !empty($provider_defaults[$provider]['premium']))){ loginizer_feature_available($provider_defaults[$provider]['name'] . ' Social Login Provider'); loginizer_page_footer(); return; } if(!empty($_POST['client_id'])){ if(!check_admin_referer('loginizer_social_nonce', 'security')){ $error[] = __('Security Check failed', 'loginizer'); } else { if(empty($provider_settings[$provider])){ $provider_settings[$provider] = []; // initializing $provider_settings[$provider]['tested'] = false; } // Need to make the user to test the login again if the key gets changed, if keys gets dirty if((isset($provider_settings[$provider]['client_id']) && $provider_settings[$provider]['client_id'] != sanitize_text_field(wp_unslash($_POST['client_id']))) || (isset($provider_settings[$provider]['client_secret']) && $provider_settings[$provider]['client_secret'] != sanitize_text_field(wp_unslash($_POST['client_secret'])))){ $provider_settings[$provider]['tested'] = false; } $provider_settings[$provider]['client_id'] = !empty($_POST['client_id']) ? sanitize_text_field(wp_unslash($_POST['client_id'])) : ''; $provider_settings[$provider]['client_secret'] = !empty($_POST['client_secret']) ? sanitize_text_field(wp_unslash($_POST['client_secret'])) : ''; $provider_settings[$provider]['enabled'] = !empty($_POST['provider_enabled']) ? true : false; $provider_settings[$provider]['button_style'] = !empty($_POST['button_style']) ? lz_optpost('button_style') : 'default'; update_option('loginizer_provider_settings', $provider_settings); $saved = true; } if(!empty($error)){ lz_report_error($error); } if(!empty($saved)){ echo '

' . esc_html__('The settings were saved successfully', 'loginizer') . '


'; } } if(!empty($provider_settings[$provider]) && !empty($provider_settings[$provider]['enabled']) && empty($provider_settings[$provider]['tested'])){ $social_nonce = wp_create_nonce('loginizer_social_check'); wp_register_script('loginizer-social-test', '', ['jquery'], LOGINIZER_VERSION, ['strategy' => false, 'in_footer' => true]); wp_enqueue_script('loginizer-social-test'); wp_add_inline_script('loginizer-social-test', ' function loginizer_auth_test_popup() { let screenX = window.screenX !== undefined ? window.screenX : window.screenLeft, screenY = window.screenY !== undefined ? window.screenY : window.screenTop, outer_width = window.outerWidth !== undefined ? window.outerWidth : document.documentElement.clientWidth, outer_height = window.outerHeight !== undefined ? window.outerHeight : document.documentElement.clientHeight - 22, target_width = 600, target_height = 600, right = parseInt(screenY + (outer_height - target_height) / 2.5, 10), left = parseInt(screenX + (outer_width - target_width) / 2, 10), attributes = []; if (target_width !== null) { attributes.push("width=" + target_width); } if (target_height !== null) { attributes.push("height=" + target_height); } attributes.push("left=" + left); attributes.push("top=" + right); attributes.push("scrollbars=1"); var social_window = window.open("'.esc_url(wp_login_url()).'?lz_social_provider='.esc_html($provider).'&test=true&social_security='.esc_html($social_nonce).'&ref='.esc_url($_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']).'", "socialWindow", attributes.join(",")); if(window.focus){ social_window.focus(); } return false; }' ); } echo '← '.esc_html__('Go Back', 'loginizer').''; if(!empty($provider_settings[$provider]) && !empty($provider_settings[$provider]['enabled']) && empty($provider_settings[$provider]['tested'])){ echo '

'.sprintf(esc_html__('Test %s to be able to use it', 'loginizer'), esc_html($provider_defaults[$provider]['name'])).'

'.esc_html__('You have added/updated the keys of this provider so you will need to test it to be able to use it.', 'loginizer').'

'.esc_html__('Please use the same email with which you have created this account.', 'loginizer').'

'; } echo '

'.esc_html($provider_defaults[$provider]['name']).' Settings

'; $styles = $provider_defaults[$provider]['styles']; foreach($styles as $style => $style_css){ echo ''; } echo '
'; wp_nonce_field('loginizer_social_nonce', 'security'); echo '
'; if(function_exists('loginizer_how_to_'.$provider)){ call_user_func('loginizer_how_to_'.$provider); } else { echo '

No Documentation Present

'; } echo '
'; loginizer_page_footer(); } // General Settings Page function loginizer_general_settings(){ $social_settings = get_option('loginizer_social_settings', []); if(!empty($_POST['security'])){ if(!check_admin_referer('loginizer_social_nonce', 'security')){ $error[] = __('Security Check failed', 'loginizer'); } else if(isset($_POST['general_settings'])){ $social_settings['general']['target_window'] = !empty($_POST['target_window']) ? lz_optpost('target_window') : false; } else if(isset($_POST['login_settings'])){ $social_settings['login']['login_form'] = lz_optpost('login_form'); $social_settings['login']['button_style'] = lz_optpost('button_style'); $social_settings['login']['button_shape'] = lz_optpost('button_shape'); $social_settings['login']['button_position'] = lz_optpost('button_position'); } $social_settings = apply_filters('loginizer_social_general_settings', $social_settings); $social_settings = map_deep($social_settings, 'sanitize_text_field'); update_option('loginizer_social_settings', $social_settings); } echo ' 0){ check_admin_referer('loginizer-options'); } // BEGIN THEME loginizer_page_header('Brute Force Settings'); // Load the blacklist and whitelist $loginizer['blacklist'] = get_option('loginizer_blacklist'); $loginizer['whitelist'] = get_option('loginizer_whitelist'); // Disable Brute Force if(isset($_POST['disable_brute_lz'])){ // Save the options update_option('loginizer_disable_brute', 1); $loginizer['disable_brute'] = 1; echo '

' . __('The Brute Force Protection feature is now disabled', 'loginizer') . '


'; } // Enable brute force if(isset($_POST['enable_brute_lz'])){ // Save the options update_option('loginizer_disable_brute', 0); $loginizer['disable_brute'] = 0; echo '

' . __('The Brute Force Protection feature is now enabled', 'loginizer') . '


'; } if(isset($_POST['save_lz_login_email'])){ $login_email['enable'] = (int) lz_optpost('loginizer_login_mail_enable'); $login_email['disable_whitelist'] = (int) lz_optpost('loginizer_login_mail_disable_whitelist'); $login_email['html_mail'] = (!empty(lz_optpost('loginizer_notify_html_mail')) ? true : false); $login_email['subject'] = sanitize_textarea_field(wp_unslash($_POST['loginizer_login_mail_subject'])); $login_email['body'] = wp_kses_post(wp_unslash($_POST['loginizer_login_mail_body'])); $login_email['roles'] = !empty($_POST['loginizer_login_mail_roles']) ? map_deep($_POST['loginizer_login_mail_roles'], 'sanitize_text_field') : []; // Save the options update_option('loginizer_login_mail', $login_email); $loginizer['login_mail'] = $login_email; // Mark as saved $GLOBALS['lz_saved'] = true; } // The Brute Force Settings if(isset($_POST['save_lz'])){ $max_retries = (int) lz_optpost('max_retries'); $lockout_time = (int) lz_optpost('lockout_time'); $max_lockouts = (int) lz_optpost('max_lockouts'); $lockouts_extend = (int) lz_optpost('lockouts_extend'); $reset_retries = (int) lz_optpost('reset_retries'); $notify_email = (int) lz_optpost('notify_email'); $notify_email_address = lz_optpost('notify_email_address'); $trusted_ips = lz_optpost('trusted_ips'); $blocked_screen = lz_optpost('blocked_screen'); if(!empty($notify_email_address) && !lz_valid_email($notify_email_address)){ $error[] = __('Email address is invalid', 'loginizer'); } if(empty(loginizer_is_whitelisted()) && isset($_POST['trusted_ips'])){ $error[] = __('Add your IP to whitelist to enable Trusted IP\'s', 'loginizer'); } if(!empty($max_retries) && $max_retries < 0){ $error[] = __('Max Retries value is invalid', 'loginizer'); } if(!empty($lockout_time) && $lockout_time < 0){ $error[] = __('Lockout Time value is invalid', 'loginizer'); } if(!empty($max_lockouts) && $max_lockouts < 0){ $error[] = __('Max Lockouts value is invalid', 'loginizer'); } if(!empty($lockouts_extend) && $lockouts_extend < 0){ $error[] = __('Extended Lockout value is invalid', 'loginizer'); } if(!empty($reset_retries) && $reset_retries < 0){ $error[] = __('Reset Retries value is invalid', 'loginizer'); } if(!empty($notify_email) && $notify_email < 0){ $error[] = __('Email Notification value is invalid', 'loginizer'); } $lockout_time = $lockout_time * 60; $lockouts_extend = $lockouts_extend * 60 * 60; $reset_retries = $reset_retries * 60 * 60; if(empty($error)){ $option['max_retries'] = $max_retries; $option['lockout_time'] = $lockout_time; $option['max_lockouts'] = $max_lockouts; $option['lockouts_extend'] = $lockouts_extend; $option['reset_retries'] = $reset_retries; $option['notify_email'] = $notify_email; $option['notify_email_address'] = $notify_email_address; $option['trusted_ips'] = $trusted_ips; $option['blocked_screen'] = $blocked_screen; // Save the options update_option('loginizer_options', $option); $saved = true; }else{ lz_report_error($error); } if(!empty($notice)){ lz_report_notice($notice); } if(!empty($saved)){ echo '

' . __('The settings were saved successfully', 'loginizer') . '


'; } } // Delete a Blackist IP range if(isset($_POST['bdelid'])){ $delid = (int) lz_optreq('bdelid'); // Unset and save $blacklist = $loginizer['blacklist']; unset($blacklist[$delid]); update_option('loginizer_blacklist', $blacklist); echo '

' . __('The Blacklist IP range has been deleted successfully', 'loginizer') . '


'; } // Delete all Blackist IP ranges if(isset($_POST['del_all_blacklist'])){ // Unset and save update_option('loginizer_blacklist', array()); echo '

' . __('The Blacklist IP range(s) have been cleared successfully', 'loginizer') . '


'; } // Delete a Whitelist IP range if(isset($_POST['delid'])){ $delid = (int) lz_optreq('delid'); // Unset and save $whitelist = $loginizer['whitelist']; unset($whitelist[$delid]); update_option('loginizer_whitelist', $whitelist); echo '

' . __('The Whitelist IP range has been deleted successfully', 'loginizer') . '


'; } // Delete all Blackist IP ranges if(isset($_POST['del_all_whitelist'])){ // Unset and save update_option('loginizer_whitelist', array()); echo '

' . __('The Whitelist IP range(s) have been cleared successfully', 'loginizer') . '


'; } // Reset All Logs if(isset($_POST['lz_reset_all_ip'])){ $result = $wpdb->query("DELETE FROM `".$wpdb->prefix."loginizer_logs` WHERE `time` > 0"); echo '

' . __('All the IP Logs have been cleared', 'loginizer') . '


'; } // Reset Logs if(isset($_POST['lz_reset_ip']) && isset($_POST['lz_reset_ips']) && is_array($_POST['lz_reset_ips'])){ $ips = $_POST['lz_reset_ips']; foreach($ips as $ip){ if(!lz_valid_ip($ip)){ $error[] = 'The IP - '.esc_html($ip).' is invalid !'; } } if(count($ips) < 1){ $error[] = __('There are no IPs submitted', 'loginizer'); } // Should we start deleting logs if(empty($error)){ foreach($ips as $ip){ $result = $wpdb->query($wpdb->prepare("DELETE FROM `".$wpdb->prefix."loginizer_logs` WHERE `ip` = %s", $ip)); } if(empty($error)){ echo '

' . __('The selected IP Logs have been reset', 'loginizer') . '


'; } } if(!empty($error)){ lz_report_error($error);echo '
'; } } if(isset($_POST['blacklist_iprange'])){ $start_ip = lz_optpost('start_ip'); $end_ip = lz_optpost('end_ip'); // If no end IP we consider only 1 IP if(empty($end_ip)){ $end_ip = $start_ip; } // Validate the IP against all checks loginizer_iprange_validate($start_ip, $end_ip, $loginizer['blacklist'], $error); if(empty($error)){ $blacklist = $loginizer['blacklist']; $newid = ( empty($blacklist) ? 0 : max(array_keys($blacklist)) ) + 1; $blacklist[$newid] = array(); $blacklist[$newid]['start'] = $start_ip; $blacklist[$newid]['end'] = $end_ip; $blacklist[$newid]['time'] = time(); update_option('loginizer_blacklist', $blacklist); echo '

' . __('Blacklist IP range added successfully', 'loginizer') . '


'; } if(!empty($error)){ lz_report_error($error);echo '
'; } } if(isset($_POST['whitelist_iprange'])){ $start_ip = lz_optpost('start_ip_w'); $end_ip = lz_optpost('end_ip_w'); // If no end IP we consider only 1 IP if(empty($end_ip)){ $end_ip = $start_ip; } // Validate the IP against all checks loginizer_iprange_validate($start_ip, $end_ip, $loginizer['whitelist'], $error); if(empty($error)){ $whitelist = $loginizer['whitelist']; $newid = ( empty($whitelist) ? 0 : max(array_keys($whitelist)) ) + 1; $whitelist[$newid] = array(); $whitelist[$newid]['start'] = $start_ip; $whitelist[$newid]['end'] = $end_ip; $whitelist[$newid]['time'] = time(); update_option('loginizer_whitelist', $whitelist); echo '

' . __('Whitelist IP range added successfully', 'loginizer') . '


'; } if(!empty($error)){ lz_report_error($error);echo '
'; } } if(isset($_POST['lz_import_csv'])){ if(!empty($_FILES['lz_import_file_csv']['name'])){ $lz_csv_type = lz_optpost('lz_csv_type'); // Is the submitted type in the allowed list ? if(!in_array($lz_csv_type, array('blacklist', 'whitelist'))){ $error[] = __('Invalid import type', 'loginizer'); } if(empty($error)){ //Get the extension of the file $csv_file_name = basename($_FILES['lz_import_file_csv']['name']); $csv_ext_name = strtolower(pathinfo($csv_file_name, PATHINFO_EXTENSION)); //Check if it's a csv file if($csv_ext_name == 'csv'){ $file = fopen($_FILES['lz_import_file_csv']['tmp_name'], "r"); $line_count = 0; $update_record = 0; while($content = fgetcsv($file)){ //Increment the $line_count $line_count++; //Skip the first line if($line_count <= 1){ continue; } if(loginizer_iprange_validate($content[0], $content[1], $loginizer[$lz_csv_type], $error, $line_count)){ $newid = ( empty($loginizer[$lz_csv_type]) ? 0 : max(array_keys($loginizer[$lz_csv_type])) ) + 1; $loginizer[$lz_csv_type][$newid] = array(); $loginizer[$lz_csv_type][$newid]['start'] = $content[0]; $loginizer[$lz_csv_type][$newid]['end'] = $content[1]; $loginizer[$lz_csv_type][$newid]['time'] = time(); $update_record = 1; } } fclose($file); if(!empty($update_record)){ update_option('loginizer_'.$lz_csv_type, $loginizer[$lz_csv_type]); echo '

' . __('Imported '.ucfirst($lz_csv_type).' IP range(s) successfully', 'loginizer') . '


'; } if(!empty($error)){ lz_report_error($error);echo '
'; } } } } } //Brute Force Bulk Blacklist/ Whitelist Ip if(isset($_POST['lz_blacklist_selected_ip'])){ if(isset($_POST['lz_reset_ips']) && is_array($_POST['lz_reset_ips'])){ $ips = $_POST['lz_reset_ips']; foreach($ips as $ip){ if(!lz_valid_ip($ip)){ $error[] = sprintf(__('The IP - %s is invalid !', 'loginizer'), esc_html($ip)); } } if(count($ips) < 1){ $error[] = __('There are no IPs submitted', 'loginizer'); } // Should we start deleting logs if(empty($error)){ $update_record = 0; foreach($ips as $ip){ if(loginizer_iprange_validate($ip, '', $loginizer['blacklist'], $error)){ $newid = ( empty($loginizer['blacklist']) ? 0 : max(array_keys($loginizer['blacklist'])) ) + 1; $loginizer['blacklist'][$newid] = array(); $loginizer['blacklist'][$newid]['start'] = $ip; $loginizer['blacklist'][$newid]['end'] = $ip; $loginizer['blacklist'][$newid]['time'] = time(); $update_record = 1; } } if(!empty($update_record)){ update_option('loginizer_blacklist', $loginizer['blacklist']); echo '

' . __('The selected IP(s) have been blacklisted', 'loginizer') . '


'; } } }else{ $error[] = __('No IP(s) selected', 'loginizer'); } if(!empty($error)){ lz_report_error($error);echo '
'; } } // Save the messages if(isset($_POST['save_err_msgs_lz'])){ $msgs['inv_userpass'] = lz_optpost('msg_inv_userpass'); $msgs['ip_blacklisted'] = lz_optpost('msg_ip_blacklisted'); $msgs['attempts_left'] = lz_optpost('msg_attempts_left'); $msgs['lockout_err'] = lz_optpost('msg_lockout_err'); $msgs['minutes_err'] = lz_optpost('msg_minutes_err'); $msgs['hours_err'] = lz_optpost('msg_hours_err'); // Update them update_option('loginizer_msg', $msgs); echo '

' . __('Error messages were saved successfully', 'loginizer') . '


'; } // Count the Results $tmp = lz_selectquery("SELECT COUNT(*) AS num FROM `".$wpdb->prefix."loginizer_logs`"); //print_r($tmp); // Which Page is it $lz_env['res_len'] = 10; $lz_env['cur_page'] = lz_get_page('lzpage', $lz_env['res_len']); $lz_env['num_res'] = $tmp['num']; $lz_env['max_page'] = ceil($lz_env['num_res'] / $lz_env['res_len']); // Get the logs $result = lz_selectquery("SELECT * FROM `".$wpdb->prefix."loginizer_logs` ORDER BY `time` DESC LIMIT ".$lz_env['cur_page'].", ".$lz_env['res_len']."", 1); //print_r($result); $lz_env['cur_page'] = ($lz_env['cur_page'] / $lz_env['res_len']) + 1; $lz_env['cur_page'] = $lz_env['cur_page'] < 1 ? 1 : $lz_env['cur_page']; $lz_env['next_page'] = ($lz_env['cur_page'] + 1) > $lz_env['max_page'] ? $lz_env['max_page'] : ($lz_env['cur_page'] + 1); $lz_env['prev_page'] = ($lz_env['cur_page'] - 1) < 1 ? 1 : ($lz_env['cur_page'] - 1); // Reload the settings $loginizer['blacklist'] = get_option('loginizer_blacklist'); $loginizer['whitelist'] = get_option('loginizer_whitelist'); $saved_msgs = get_option('loginizer_msg'); ?>

'.__('Failed Login Attempts Logs', 'loginizer').'   ('.__('Past', 'loginizer').' '.($loginizer['reset_retries']/60/60).' '.__('hours', 'loginizer').')'; ?>

'; }else{ foreach($result as $ik => $iv){ $status_button = (!empty($iv['status']) ? 'disable' : 'enable'); echo ' '; } } ?>
'.__('No Logs. You will see logs about failed login attempts here.', 'loginizer').'
'.esc_html($iv['ip']).'  '.esc_html($iv['username']).' '.date('d/M/Y H:i:s P', $iv['time']).' '.esc_html($iv['count']).' '.esc_html($iv['lockout']).' '.esc_html($iv['url']).'

           






0 to disable email notifications','loginizer'); ?>

name="trusted_ips" id="trusted_ips"/>
name="blocked_screen" id="blocked_screen"/>

'; }else{ echo ''; } ?>


If you want to blacklist single IP leave this field blank.','loginizer'); ?>

'; }else{ foreach($loginizer['blacklist'] as $ik => $iv){ echo ' '; } } ?>
'.__('No Blacklist IPs. You will see blacklisted IP ranges here.', 'loginizer').'
'.$iv['start'].' '.$iv['end'].' '.date('d/m/Y', $iv['time']).' Delete



If you want to whitelist single IP leave this field blank.','loginizer'); ?>

'; }else{ foreach($loginizer['whitelist'] as $ik => $iv){ echo ' '; } } ?>
'.__('No Whitelist IPs. You will see whitelisted IP ranges here.', 'loginizer').'
'.$iv['start'].' '.$iv['end'].' '.date('d/m/Y', $iv['time']).' Delete

"' . $loginizer['d_msg']['inv_userpass']. '"', 'loginizer'); ?>
"' . $loginizer['d_msg']['ip_blacklisted']. '"', 'loginizer'); ?>
"' . $loginizer['d_msg']['attempts_left']. '"', 'loginizer'); ?>
"' . strip_tags($loginizer['d_msg']['lockout_err']). '"', 'loginizer'); ?>
"' . strip_tags($loginizer['d_msg']['minutes_err']). '"', 'loginizer'); ?>
"' . strip_tags($loginizer['d_msg']['hours_err']). '"', 'loginizer'); ?>

New' : '');?>


/>

/>
/>


Default :

Variables :
$sitename - The Site Name
$user_login - User Name


Default :

Variables :
$sitename - The Site Name
$user_login - User Name
$date - Time and Date ( current date and time of Login )
$ip - Device IP Address from which login happned

'; foreach($editable_roles as $role => $details) { $name = translate_user_role($details['name']); // Preselect specified role. if((!empty($loginizer['login_mail']['roles']) && in_array($role, $loginizer['login_mail']['roles'])) || (!empty($_POST['loginizer_login_mail_roles']) && in_array($role, $_POST['loginizer_login_mail_roles']))){ echo ''.esc_html($name).''; } else { echo ''.esc_html($name).''; } echo '
'; } echo ''; ?>

inet_ptoi($end_ip)){ // BUT, if 0.0.0.1 - 255.255.255.255 is given, it will not work if(inet_ptoi($start_ip) >= 0 && inet_ptoi($end_ip) < 0){ // This is right }else{ $cur_error[] = __('The End IP cannot be smaller than the Start IP', 'loginizer').$line_error; } } if(!empty($cur_error)){ foreach($cur_error as $rk => $rv){ $error[] = $rv; } return false; } if(!empty($cur_list)){ foreach($cur_list as $k => $v){ // This is to check if there is any other range exists with the same Start or End IP if(( inet_ptoi($start_ip) <= inet_ptoi($v['start']) && inet_ptoi($v['start']) <= inet_ptoi($end_ip) ) || ( inet_ptoi($start_ip) <= inet_ptoi($v['end']) && inet_ptoi($v['end']) <= inet_ptoi($end_ip) ) ){ $cur_error[] = __('The Start IP or End IP submitted conflicts with an existing IP range !', 'loginizer').$line_error; break; } // This is to check if there is any other range exists with the same Start IP if(inet_ptoi($v['start']) <= inet_ptoi($start_ip) && inet_ptoi($start_ip) <= inet_ptoi($v['end'])){ $cur_error[] = __('The Start IP is present in an existing range !', 'loginizer').$line_error; break; } // This is to check if there is any other range exists with the same End IP if(inet_ptoi($v['start']) <= inet_ptoi($end_ip) && inet_ptoi($end_ip) <= inet_ptoi($v['end'])){ $cur_error[] = __('The End IP is present in an existing range!', 'loginizer').$line_error; break; } } } if(!empty($cur_error)){ foreach($cur_error as $rk => $rv){ $error[] = $rv; } return false; } return true; }main/settings/dashboard.php000064400000034437147577535410012025 0ustar00 0){ check_admin_referer('loginizer-options'); } do_action('loginizer_pre_page_dashboard'); // Is there a IP Method ? if(isset($_POST['save_lz_ip_method'])){ $ip_method = (int) lz_optpost('lz_ip_method'); $custom_ip_method = lz_optpost('lz_custom_ip_method'); if($ip_method >= 0 && $ip_method <= 3){ update_option('loginizer_ip_method', $ip_method); } // Custom Method name ? if($ip_method == 3){ update_option('loginizer_custom_ip_method', $custom_ip_method); } } loginizer_page_dashboard_T(); } // The Loginizer Admin Options Page - THEME function loginizer_page_dashboard_T(){ global $loginizer, $lz_error, $lz_env; loginizer_page_header('Dashboard'); ?>

  '.__('Your Server IP Address seems to match the Client IP detected by Loginizer. You might want to change the IP detection method to HTTP_X_FORWARDED_FOR under System Information section.', 'loginizer').'


'; } loginizer_newsletter_subscribe(); if(!empty($loginizer['backuply_promo']) && $loginizer['backuply_promo'] > 0 && $loginizer['backuply_promo'] < (time() - (7*24*3600))){ loginizer_backuply_promo(); } echo '
'. __('Thank you for choosing Loginizer! Many more features coming soon...   Review Loginizer at WordPress    ', 'loginizer').''. __('Add Review', 'loginizer'). '

'; // Saved ? if(!empty($GLOBALS['lz_saved'])){ echo '

'. __('The settings were saved successfully', 'loginizer'). '


'; } // Any errors ? if(!empty($lz_error)){ lz_report_error($lz_error);echo '
'; } ?>

Brute Force Protection is immediately enabled. You should start by going over the default settings and tweaking them as per your needs.', 'loginizer'); ?> '.__('In the Premium version of Loginizer you have many more features. We recommend you enable features like reCAPTCHA, Two Factor Auth or Email based PasswordLess login. These features will improve your websites security','loginizer').''; }else{ echo '
'.__('Upgrade to Pro for more features like reCAPTCHA, Two Factor Auth, Rename wp-admin and wp-login.php pages, Email based PasswordLess login and more. These features will improve your website\'s security.','loginizer').''; } ?>
$count){ if($attempt_time < strtotime('-30 days')){ unset($login_attempt_stats[$attempt_time]); update_option('loginizer_login_attempt_stats', $login_attempt_stats, false); continue; } $day_month = date('M j', $attempt_time); if(empty($stats_dataset[$day_month])){ $stats_dataset[$day_month] = 0; } if(!empty($login_attempt_stats[$attempt_time][0])){ $stats_dataset[$day_month] += $login_attempt_stats[$attempt_time][0]; } if($attempt_time > strtotime('-24 hours')){ if(!empty($login_attempt_stats[$attempt_time][0])){ $failed_logins += $login_attempt_stats[$attempt_time][0]; } if(!empty($login_attempt_stats[$attempt_time][1])){ $success_logins += $login_attempt_stats[$attempt_time][1]; } continue; } } $failed_login_color = '#f9fa8e'; if($failed_logins < 40){ $failed_login_color = '#f9fa8e'; $failed_notice = __('Your Website is safe', 'loginizer'); } else if($failed_logins < 70){ $failed_login_color = '#ffcd56'; $failed_notice = __('Risk from Brute-force attacks is low, attacks are under control', 'loginizer'); } else if($failed_logins < 150){ $failed_login_color = '#f67019'; $failed_notice = __('Brute-force attacks on your websites are on rise', 'loginizer'); } else { $failed_login_color = '#fc1e4d'; $failed_notice = __('Your website is under heavy brute-force attacks.
Upgrade to a premium version for added protection if this trend persists. Act fast to secure your site.', 'loginizer'); } if(defined('LOGINIZER_PREMIUM')){ $failed_login_color = '#f53794'; $failed_notice = __('Your website is being protected by Loginizer Security.', 'loginizer'); } ?>

'; do_action('loginizer_system_information'); echo ''; if(file_exists(ABSPATH.'/.htaccess')){ echo ' '; } // Setting up the dataset for the 30 day chart $line_dataset[] = array( 'label' => __( 'Failed', 'loginizer'), 'data' => array_reverse($stats_dataset), 'backgroundColor' => 'rgb(54, 162, 235)', 'borderColor' => 'rgb(54, 162, 235)', ); // Enqueues CharJS script and inline the char js wp_enqueue_script('chartjs', LOGINIZER_URL.'/assets/js/chart.js', array('jquery'), '3.0.0'); wp_add_inline_script('chartjs', 'function lz_attempts_chart(){ const ctx = document.getElementById("lz-attempts-chart"); new Chart(ctx, { type: "doughnut", data: { labels: ["Failed", "Success"], datasets: [{ label: "Count", data: ['.esc_html($failed_logins).', '.esc_html($success_logins).'], backgroundColor: [ "'.esc_html($failed_login_color).'", "rgb(54, 162, 235)", ], hoverOffset: 4, borderWidth: [0] }], }, options : { circumference : 180, rotation:-90, responsive: true, } }); const thirty_days = document.getElementById("lz-attemt-chart-thirty"); new Chart(thirty_days, { type: "line", data: { datasets: '.json_encode($line_dataset).' }, options: { responsive: true, maintainAspectRatio: false, hover: { mode: "nearest", intersect: true }, scales: { x: { display: true, scaleLabel: { display: false } }, y: { display: true, scaleLabel: { display: false }, beginAtZero: true, ticks: { callback: function(label, index, labels) { if (Math.floor(label) === label) { return label; } }, } } } } }); } lz_attempts_chart();'); ?>
'.__('Loginizer Version', 'loginizer').' '.LOGINIZER_VERSION.(defined('LOGINIZER_PREMIUM') ? ' ('.__('Security PRO Version','loginizer').')' : '').'
'.__('URL', 'loginizer').' '.get_site_url().'
'.__('Path', 'loginizer').' '.ABSPATH.'
'.__('Server\'s IP Address', 'loginizer').' '.@$_SERVER['SERVER_ADDR'].'
'.__('Your IP Address', 'loginizer').' '.lz_getip().'
Method :
'.__('wp-config.php is writable', 'loginizer').' '.(is_writable(ABSPATH.'/wp-config.php') ? 'Yes' : 'No').'
'.__('.htaccess is writable', 'loginizer').' '.(is_writable(ABSPATH.'/.htaccess') ? 'Yes' : 'No').'

'; if(version_compare(phpversion(), '7.0') < 0){ $wp_content = basename(dirname(dirname(dirname(dirname(dirname(__FILE__)))))); }else { $wp_content = basename(dirname(__FILE__, 5)); } $files_to_check = array('/' => array('0755', '0750'), '/wp-admin' => array('0755'), '/wp-includes' => array('0755'), '/wp-config.php' => array('0444'), '/'.$wp_content => array('0755'), '/'.$wp_content.'/themes' => array('0755'), '/'.$wp_content.'/plugins' => array('0755')); if(file_exists(ABSPATH.'/.htaccess')){ $files_to_check['.htaccess'] = array('0444'); } $root = ABSPATH; foreach($files_to_check as $k => $v){ $path = $root.'/'.$k; $stat = @stat($path); $suggested = $v; $actual = substr(sprintf('%o', $stat['mode']), -4); echo ' '; } ?>
'.__('Relative Path', 'loginizer').' '.__('Suggested', 'loginizer').' '.__('Actual', 'loginizer').'
'.$k.' '.current($suggested).' '.$actual.'
0){ $lz_error['not_in_free'] = __('This feature is not available in the Free version. Upgrade to Pro', 'loginizer'); return loginizer_page_2fa_T(); } $lz_roles = get_editable_roles(); if(empty($lz_roles)){ $lz_roles = array(); } /* Make sure post was from this page */ if(count($_POST) > 0){ check_admin_referer('loginizer-options'); } // Settings submitted if(isset($_POST['save_lz'])){ // In the future there can be more settings $option['2fa_app'] = (int) lz_optpost('2fa_app'); $option['2fa_email'] = (int) lz_optpost('2fa_email'); $option['question'] = (int) lz_optpost('question'); $option['2fa_email_force'] = (int) lz_optpost('2fa_email_force'); // Any roles to apply to ? foreach($lz_roles as $k => $v){ if(lz_optpost('2fa_roles_'.$k)){ $option['2fa_roles'][$k] = 1; } } // If its all, then blank it if(lz_optpost('2fa_roles_all') || empty($option['2fa_roles'])){ $option['2fa_roles'] = ''; } // Is there an error ? if(!empty($lz_error)){ return loginizer_page_2fa_T(); } // Save the options update_option('loginizer_2fa', $option); // Mark as saved $GLOBALS['lz_saved'] = true; // update the rewrite rules for WooCommerce to make security settings page accessible from woo commerce client area if((!empty($option['2fa_app']) || !empty($option['2fa_email']) || !empty($option['question']) || !empty($option['2fa_email_force'])) && class_exists('WooCommerce')){ loginizer_woocommerce_rewrite_rule(); } } // Reset a users 2FA if(isset($_POST['reset_user_lz'])){ $_username = lz_optpost('lz_user_2fa_disable'); // Try to get the user $user_search = get_user_by('login', $_username); // If not found then search by email if(empty($user_search)){ $user_search = get_user_by('email', $_username); } // If not found then give error if(empty($user_search)){ $lz_error['2fa_user_not'] = __('There is no such user with the email or username you submitted', 'loginizer'); return loginizer_page_2fa_T(); } // Get the user prefences $user_pref = get_user_meta($user_search->ID, 'loginizer_user_settings'); // Blank it $user_pref['pref'] = 'none'; // Save it update_user_meta($user_search->ID, 'loginizer_user_settings', $user_pref); // Mark as saved $GLOBALS['lz_saved'] = __('The user\'s 2FA settings have been reset', 'loginizer'); } if(isset($_POST['save_2fa_custom_redirect'])){ if(!empty($_POST['lz_2fa_custom_login_redirect'])){ $loginizer['2fa_custom_login_redirect'] = map_deep($_POST['lz_2fa_custom_login_redirect'], 'sanitize_text_field'); update_option('loginizer_2fa_custom_redirect', $loginizer['2fa_custom_login_redirect']); $GLOBALS['lz_saved'] = true; } } if(isset($_POST['save_2fa_email_template_lz'])){ // In the future there can be more settings $option['2fa_email_sub'] = @stripslashes($_POST['lz_2fa_email_sub']); $option['2fa_email_msg'] = @stripslashes($_POST['lz_2fa_email_msg']); // Is there an error ? if(!empty($lz_error)){ return loginizer_page_2fa_T(); } // Save the options update_option('loginizer_2fa_email_template', $option); // Mark as saved $GLOBALS['lz_saved'] = true; } // Save the messages if(isset($_POST['save_msgs_lz'])){ $msgs['otp_app'] = lz_optpost('msg_otp_app'); $msgs['otp_email'] = lz_optpost('msg_otp_email'); $msgs['otp_field'] = lz_optpost('msg_otp_field'); $msgs['otp_question'] = lz_optpost('msg_otp_question'); $msgs['otp_answer'] = lz_optpost('msg_otp_answer'); // Update them update_option('loginizer_2fa_msg', $msgs); // Mark as saved $GLOBALS['lz_saved'] = __('Messages were saved successfully', 'loginizer'); } // Delete a Whitelist IP range if(isset($_POST['delid'])){ $delid = (int) lz_optreq('delid'); // Unset and save $whitelist = $loginizer['2fa_whitelist']; unset($whitelist[$delid]); update_option('loginizer_2fa_whitelist', $whitelist); // Mark as saved $GLOBALS['lz_saved'] = __('The Whitelist IP range has been deleted successfully', 'loginizer'); } // Delete all Blackist IP ranges if(isset($_POST['del_all_whitelist'])){ // Unset and save update_option('loginizer_2fa_whitelist', array()); // Mark as saved $GLOBALS['lz_saved'] = __('The Whitelist IP range(s) have been cleared successfully', 'loginizer'); } // Add IP range to 2FA whitelist if(isset($_POST['2fa_whitelist_iprange'])){ $start_ip = lz_optpost('start_ip_w_2fa'); $end_ip = lz_optpost('end_ip_w_2fa'); if(empty($start_ip)){ $lz_error[] = __('Please enter the Start IP', 'loginizer'); return loginizer_page_2fa_T(); } // If no end IP we consider only 1 IP if(empty($end_ip)){ $end_ip = $start_ip; } if(!lz_valid_ip($start_ip)){ $lz_error[] = __('Please provide a valid start IP', 'loginizer'); } if(!lz_valid_ip($end_ip)){ $lz_error[] = __('Please provide a valid end IP', 'loginizer'); } if(inet_ptoi($start_ip) > inet_ptoi($end_ip)){ // BUT, if 0.0.0.1 - 255.255.255.255 is given, it will not work if(inet_ptoi($start_ip) >= 0 && inet_ptoi($end_ip) < 0){ // This is right }else{ $lz_error[] = __('The End IP cannot be smaller than the Start IP', 'loginizer'); } } if(empty($lz_error)){ $whitelist = $loginizer['2fa_whitelist']; foreach($whitelist as $k => $v){ // This is to check if there is any other range exists with the same Start or End IP if(( inet_ptoi($start_ip) <= inet_ptoi($v['start']) && inet_ptoi($v['start']) <= inet_ptoi($end_ip) ) || ( inet_ptoi($start_ip) <= inet_ptoi($v['end']) && inet_ptoi($v['end']) <= inet_ptoi($end_ip) ) ){ $lz_error[] = __('The Start IP or End IP submitted conflicts with an existing IP range !', 'loginizer'); break; } // This is to check if there is any other range exists with the same Start IP if(inet_ptoi($v['start']) <= inet_ptoi($start_ip) && inet_ptoi($start_ip) <= inet_ptoi($v['end'])){ $lz_error[] = __('The Start IP is present in an existing range !', 'loginizer'); break; } // This is to check if there is any other range exists with the same End IP if(inet_ptoi($v['start']) <= inet_ptoi($end_ip) && inet_ptoi($end_ip) <= inet_ptoi($v['end'])){ $lz_error[] = __('The End IP is present in an existing range!', 'loginizer'); break; } } $newid = ( empty($whitelist) ? 0 : max(array_keys($whitelist)) ) + 1; if(empty($lz_error)){ $whitelist[$newid] = array(); $whitelist[$newid]['start'] = $start_ip; $whitelist[$newid]['end'] = $end_ip; $whitelist[$newid]['time'] = time(); update_option('loginizer_2fa_whitelist', $whitelist); // Mark as saved $GLOBALS['lz_saved'] = __('Whitelist IP range for Two Factor Authentication added successfully', 'loginizer'); } } } $lz_options = get_option('loginizer_2fa_email_template'); $saved_msgs = get_option('loginizer_2fa_msg'); $loginizer['2fa_whitelist'] = get_option('loginizer_2fa_whitelist'); // Call theme loginizer_page_2fa_T(); } // Loginizer - Two Factor Auth Page function loginizer_page_2fa_T(){ global $loginizer, $lz_error, $lz_env, $lz_roles, $lz_options, $saved_msgs; // Universal header loginizer_page_header('Two Factor Authentication'); loginizer_feature_available('Two-Factor Authentication'); // Saved ? if(!empty($GLOBALS['lz_saved'])){ echo '

'. __(is_string($GLOBALS['lz_saved']) ? $GLOBALS['lz_saved'] : 'The settings were saved successfully', 'loginizer'). '


'; } // Any errors ? if(!empty($lz_error)){ lz_report_error($lz_error);echo '
'; } ?>


Google Authenticator, Authy, etc.', 'loginizer'); ?>
/>

/>

/>


/>

/> All
$v){ echo ' '.$v['name'].'
'; } ?>




Default :


Default :

Variables :
$otp - The OTP for login
$site_name - The Site Name
$site_url - The Site URL
$email - Users Email
$display_name - Users Display Name
$user_login - Username
$first_name - Users First Name
$last_name - Users Last Name



"' . $loginizer['2fa_d_msg']['otp_app']. '"', 'loginizer'); ?>


"' . $loginizer['2fa_d_msg']['otp_email']. '"', 'loginizer'); ?>


"' . $loginizer['2fa_d_msg']['otp_field']. '"', 'loginizer'); ?>


"' . $loginizer['2fa_d_msg']['otp_question']. '"', 'loginizer'); ?>


"' . $loginizer['2fa_d_msg']['otp_answer']. '"', 'loginizer'); ?>






If you want to whitelist single IP leave this field blank.','loginizer'); ?>

'; }else{ foreach($loginizer['2fa_whitelist'] as $ik => $iv){ echo ' '; } } ?>
'.__('No Whitelist IPs for Two Factor Authentication. You will see whitelisted IP ranges here.', 'loginizer').'
'.$iv['start'].' '.$iv['end'].' '.date('d/m/Y', $iv['time']).' Delete


roles as $key => $role){ echo''; } ?>


prefix."loginizer_logs` ORDER BY `time` DESC", 1); $filename = 'loginizer-failed-login-attempts'; if(empty($csv_array)){ echo -1; echo __('No data to export', 'loginizer'); wp_die(); } header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename='.$filename.'.csv'); $allowed_fields = array('ip' => 'IP', 'attempted_username' => 'Attempted Username', 'last_f_attemp' => 'Last Failed Attempt', 'f_attempts_count' => 'Failed Attempts Count', 'lockouts_count' => 'Lockouts Count', 'url_attacked' => 'URL Attacked'); $file = fopen("php://output","w"); fputcsv($file, array_values($allowed_fields)); foreach($csv_array as $failed_attempts){ $row = array($failed_attempts['ip'], $failed_attempts['username'], date('d/M/Y H:i:s P', $failed_attempts['time']), $failed_attempts['count'], $failed_attempts['lockout'], $failed_attempts['url']); fputcsv($file, $row); } fclose($file); wp_die(); } // Export CSV function loginizer_export(){ // Some AJAX security check_ajax_referer('loginizer_admin_ajax', 'nonce'); if(!current_user_can('manage_options')){ wp_die('Sorry, but you do not have permissions to change settings.'); } $lz_csv_type = lz_optpost('lz_csv_type'); switch($lz_csv_type){ case 'blacklist': $csv_array = get_option('loginizer_blacklist'); $filename = 'loginizer-blacklist'; break; case 'whitelist': $csv_array = get_option('loginizer_whitelist'); $filename = 'loginizer-whitelist'; break; } if(empty($csv_array)){ echo -1; echo __('No data to export', 'loginizer'); wp_die(); } header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename='.$filename.'.csv'); $allowed_fields = array('start' => 'Start IP', 'end' => 'End IP', 'time' => 'Time'); $file = fopen("php://output","w"); fputcsv($file, array_values($allowed_fields)); foreach($csv_array as $ik => $iv){ $iv['start'] = $iv['start']; $iv['end'] = $iv['end']; $iv['time'] = date('d/m/Y', $iv['time']); $row = array(); foreach($allowed_fields as $ak => $av){ $row[$ak] = $iv[$ak]; } fputcsv($file, $row); } fclose($file); wp_die(); } function loginizer_social_order(){ // Some AJAX security check_ajax_referer('loginizer_social_nonce', 'security'); if(!current_user_can('manage_options')){ wp_die(__('Sorry, but you do not have permissions to change settings.', 'loginizer')); } $order = map_deep(map_deep($_POST['order'], 'wp_unslash'), 'sanitize_text_field'); update_option('loginizer_social_order', array_flip($order)); wp_send_json_success(); } function loginizer_dismiss_license_alert(){ // Some AJAX security check_ajax_referer('loginizer_license_notice', 'security'); if(!current_user_can('manage_options')){ wp_die(__('Sorry, but you do not have permissions to change settings.', 'loginizer')); } update_option('loginizer_license_notice', (0 - time()), false); die('DONE'); } function loginizer_dismiss_softwp_alert(){ // Some AJAX security check_ajax_referer('loginizer_softwp_notice', 'security'); if(!current_user_can('activate_plugins')){ wp_die(__('Sorry, but you do not have permissions to change settings.', 'loginizer')); } update_option('loginizer_softwp_upgrade', (0 - time()), false); die('DONE'); }main/login-providers.php000064400000004315147577535410011351 0ustar00 [ 'name' => 'LinkedIn', 'color' => '#2867b2', 'icons' => [], 'styles' => [ 'default' => 'background-color:#2867b2; color:white;' ] ], 'Google' => [ 'name' => 'Google', 'color' => '#4285F4', 'icons' => [], 'premium' => true, 'styles' => [ 'default' => 'background-color:#fff; color: #1f1f1f; border: 1px solid #747775;', 'dark' => 'background-color: #131314; color: #e3e3e3; border: 1px solid #747775; border-color: #8e918f;', 'neutral' => 'background-color:#F2F2F2; color:#1f1f1f;' ] ], 'Facebook' => [ 'name' => 'Facebook', 'color' => '#0866ff', 'icons' => [], 'premium' => true, 'tls_only' => true, 'styles' => [ 'default' => 'background-color:#1877F2; color:#fff; border:1px solid #1877F2;', 'light' => 'background-color:#fff; color:#1877F2; border:1px solid #1877F2;', 'dark' => 'background-color:#000; color:#fff;', 'bodered' => 'background-color:#fff; color:#000; border:1px solid #000;' ] ], 'Twitter' => [ 'name' => 'X (formly Twitter)', 'color' => '#14171a', 'icons' => [], 'premium' => true, 'styles' => [ 'default' => 'background-color:#14171a; color:white;' ] ], 'GitHub' => [ 'name' => 'GitHub', 'color' => '#24292f', 'icons' => [], 'premium' => true, 'styles' => [ 'default' => 'background-color:#24292f; color:white;' ] ], 'TwitchTV' => [ 'name' => 'Twitch', 'color' => '#9146FF', 'icons' => [], 'premium' => true, 'styles' => [ 'default' => 'background-color:#9146FF; color:white;' ] ], 'Discord' => [ 'name' => 'Discord', 'color' => '#5865F2', 'icons' => [], 'premium' => true, 'styles' => [ 'default' => 'background-color:#5865F2; color:white;' ] ], 'WordPress' => [ 'name' => 'WordPress.Com', 'color' => '#2271b1', 'icons' => [], 'premium' => true, 'styles' => [ 'default' => 'background-color:#2271b1; color:white;' ] ], ];main/admin.php000064400000077732147577535410007333 0ustar00 0 || (abs($license_notice) + MONTH_IN_SECONDS * 2) < time())){ $current_timestamp = time(); $expiration_timestamp = strtotime($license['expires']); $timediff = $expiration_timestamp - $current_timestamp; if($timediff <= WEEK_IN_SECONDS){ add_action('admin_notices', 'loginizer_check_expires'); } } } // Notice to let user know about SoftWP. if( !defined('LOGINIZER_PREMIUM') && !defined('BACKUPLY_PRO') && !defined('SPEEDYCACHE_PRO') && !defined('PAGELAYER_PREMIUM') && !defined('GOSMTP_PREMIUM') && !defined('SITESEO_PREMIUM') ){ add_action('admin_notices', 'loginizer_softwp_upgrader_notice'); } } // Add settings link on plugin page function loginizer_plugin_action_links($links) { if(!defined('LOGINIZER_PREMIUM')){ $links[] = ''._x('Upgrade', 'Plugin action link label.', 'loginizer').''; } $settings_link = 'Settings'; array_unshift($links, $settings_link); return $links; } function loginizer_newsletter_subscribe(){ $newsletter_dismiss = get_option('loginizer_dismiss_newsletter'); if(!empty($newsletter_dismiss)){ return; } $env['url'] = 'https://loginizer.com/'; echo '

'; return true; } function loginizer_backuply_promo(){ $plugins = get_plugins(); // Dont show Backuply Promo if its already installed if(array_key_exists('backuply-pro/backuply-pro.php', $plugins) || array_key_exists('backuply/backuply.php', $plugins)){ return; } if(isset($_REQUEST['install_backuply'])){ if(!wp_verify_nonce($_REQUEST['security'], 'loginizer_install_backuply') || !current_user_can('activate_plugins')){ die('Only Admin can access it'); } loginizer_backuply_install(); return; } echo '
'.__('Backups are the best form of security. Secure your WordPress site by creating backups with Backuply','loginizer').':
  • '.__('Backup to remote locations like FTP, FTPS, SFTP, WebDAV, Google Drive, OneDrive, Dropbox, Amazon S3','loginizer').'
  • '.__('Auto Backups','loginizer').'
  • '.__('Easy One-Click restores','loginizer').'
  • '.__('Stress Free Migrations','loginizer').'
'.__('Install Backuply', 'loginizer').'  '.__('Visit Backuply','loginizer').'

'; return true; } function loginizer_csrf_promo(){ echo '

Secure your WordPress site from CSRF attacks with our new feature CSRF Protection Read More

'; echo''; } // Install Backuply function loginizer_backuply_install(){ // Include the necessary stuff include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' ); // Includes necessary for Plugin_Upgrader and Plugin_Installer_Skin include_once( ABSPATH . 'wp-admin/includes/file.php' ); include_once( ABSPATH . 'wp-admin/includes/misc.php' ); include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); // Filter to prevent the activate text add_filter('install_plugin_complete_actions', 'loginizer_backuply_install_complete_actions', 10, 3); $upgrader = new Plugin_Upgrader( new Plugin_Installer_Skin() ); $installed = $upgrader->install('https://downloads.wordpress.org/plugin/backuply.zip'); if ( !is_wp_error( $installed ) && $installed ) { echo 'Activating Backuply !'; $activate = activate_plugin('backuply/backuply.php'); if ( is_null($activate) ) { echo '

'. esc_html__('Done! Backuply is now installed and activated.', 'loginizer'). '




'. esc_html__('Done! Backuply is now installed and activated.', 'loginizer').''; } } return $installed; } // Prevent pro activate text for installer function loginizer_backuply_install_complete_actions($install_actions, $api, $plugin_file){ if($plugin_file == 'backuply/backuply.php'){ return array(); } return $install_actions; } // The Loginizer Theme footer function loginizer_page_footer(){ if(!loginizer_is_premium()){ echo ''; } echo ' '; if(!defined('SITEPAD')){ if(!defined('LOGINIZER_PREMIUM')){ echo '

'.__('Premium Version','loginizer').'

'.__('Upgrade to the premium version and get the following features','loginizer').' :
  • '.__('PasswordLess Login','loginizer').'
  • '.__('Two Factor Auth - Email','loginizer').'
  • '.__('Two Factor Auth - App','loginizer').'
  • '.__('Login Challenge Question','loginizer').'
  • '.__('reCAPTCHA','loginizer').'
  • '.__('Rename Login Page','loginizer').'
  • '.__('Disable XML-RPC','loginizer').'
  • '.__('And many more ...','loginizer').'
Upgrade
'; }else{ echo '

'.__('Recommendations','loginizer').'

'.__('We recommed that you enable atleast one of the following security features','loginizer').':
  • '.__('Rename Login Page','loginizer').'
  • '.__('Login Challenge Question','loginizer').'
  • '.__('reCAPTCHA','loginizer').'
  • '.__('Two Factor Auth - Email','loginizer').'
  • '.__('Two Factor Auth - App','loginizer').'
  • '.__('Change \'admin\' Username','loginizer').'
'; } echo '

'.__('Secure your WordPress site by creating backups with Backuply', 'loginizer').':
  • '.__('Remote Backup to 8 location','loginizer').'
  • '.__('Auto Backups', 'loginizer').'
  • '.__('Backup Rotation', 'loginizer').'
  • '.__('One-Click Restore', 'loginizer').'
  • '.__('Stress-free Migration', 'loginizer').'
  • '.__('Backup to Google Drive', 'loginizer').'
  • '.__('Backup to Amazon S3', 'loginizer').'
  • '.__('Backup to Dropbox', 'loginizer').'
  • '.__('Backup to FTP,FTPS and many more ...','loginizer').'
'.__('Visit Backuply','loginizer').'
'; echo '

'.__('Easily manage and make professional pages and content with our Pagelayer builder','loginizer').':
  • '.__('30+ Free Widgets','loginizer').'
  • '.__('60+ Premium Widgets','loginizer').'
  • '.__('400+ Premium Sections','loginizer').'
  • '.__('Theme Builder','loginizer').'
  • '.__('WooCommerce Builder','loginizer').'
  • '.__('Theme Creator and Exporter','loginizer').'
  • '.__('Form Builder','loginizer').'
  • '.__('Popup Builder','loginizer').'
  • '.__('And many more ...','loginizer').'
'.__('Visit Pagelayer','loginizer').'
'; } echo ' '; if(!defined('SITEPAD')){ echo '
'.__('Let your friends know that you have secured your website :','loginizer').'
   


Loginizer '.__('v'.LOGINIZER_VERSION.'. You can report any bugs ','loginizer').''.__('here','loginizer').'.'; } echo ' '; } // The Loginizer Admin Options Page function loginizer_page_header($title = 'Loginizer'){ global $loginizer; ?>
'; if(!defined('SITEPAD')){ echo ''; } echo '

'.$loginizer['prefix'].$title.'

'. __('Refer and Earn', 'loginizer'). ' '.__('Review Loginizer', 'loginizer').'

'; if(defined('LOGINIZER_PREMIUM') && !empty($loginizer['enable_csrf_protection']) && !loginizer_is_csrf_prot_mod_set()){ $lz_error['csrf_mod'] = esc_html__('You have enabled CSRF protection but the .htaccess file has not been updated', 'loginizer'); if(!empty($lz_error)){ lz_report_error($lz_error);echo '
'; } } } // Shows the admin menu of Loginizer function loginizer_admin_menu() { global $wp_version, $loginizer; if(!defined('SITEPAD')){ // Add the menu page add_menu_page(__('Loginizer Dashboard', 'loginizer'), __('Loginizer Security', 'loginizer'), 'activate_plugins', 'loginizer', 'loginizer_dashboard'); // Dashboard add_submenu_page('loginizer', __('Loginizer Dashboard', 'loginizer'), __('Dashboard', 'loginizer'), 'activate_plugins', 'loginizer', 'loginizer_dashboard'); }else{ // Add the menu page add_menu_page(__('Security', 'loginizer'), __('Security', 'loginizer'), 'activate_plugins', 'loginizer', 'loginizer_security_settings', 'dashicons-shield', 85); // Rename Login add_submenu_page('loginizer', __('Security Settings', 'loginizer'), __('Rename Login', 'loginizer'), 'activate_plugins', 'loginizer', 'loginizer_security_settings'); } // Brute Force add_submenu_page('loginizer', __('Brute Force Settings', 'loginizer'), __('Brute Force', 'loginizer'), 'activate_plugins', 'loginizer_brute_force', 'loginizer_brute_force_settings'); // PasswordLess add_submenu_page('loginizer', __($loginizer['prefix'].'PasswordLess Settings', 'loginizer'), __('PasswordLess', 'loginizer'), 'activate_plugins', 'loginizer_passwordless', 'loginizer_passwordless_settings'); // Security Settings if(!defined('SITEPAD')){ // Two Factor Auth add_submenu_page('loginizer', __($loginizer['prefix'].' Two Factor Authentication', 'loginizer'), __('Two Factor Auth', 'loginizer'), 'activate_plugins', 'loginizer_2fa', 'loginizer_2fa_settings'); } // reCaptcha add_submenu_page('loginizer', __($loginizer['prefix'].'reCAPTCHA Settings', 'loginizer'), __('reCAPTCHA', 'loginizer'), 'activate_plugins', 'loginizer_recaptcha', 'loginizer_recaptcha_settings'); // Temporary Login add_submenu_page('loginizer', __($loginizer['prefix'].'SSO', 'loginizer'), __('Single Sign On', 'loginizer'). ((time() < strtotime('30 November 2023')) ? ' Update' : ''), 'activate_plugins', 'loginizer_sso', 'loginizer_sso_settings'); // Social Login $hook_name = add_submenu_page('loginizer', __($loginizer['prefix'].'social_login', 'loginizer'), __('Social Login', 'loginizer') . ((time() < strtotime('30 July 2024')) ? ' New' : ''), 'activate_plugins', 'loginizer_social_login', 'loginizer_social_login_settings'); // Security Settings if(!defined('SITEPAD')){ // Security Settings add_submenu_page('loginizer', __($loginizer['prefix'].'Security Settings', 'loginizer'), __('Security Settings', 'loginizer'), 'activate_plugins', 'loginizer_security', 'loginizer_security_settings'); // File Checksums add_submenu_page('loginizer', __('Loginizer File Checksums', 'loginizer'), __('File Checksums', 'loginizer'), 'activate_plugins', 'loginizer_checksums', 'loginizer_checksums_settings'); } if(!defined('LOGINIZER_PREMIUM') && !empty($loginizer['ins_time']) && $loginizer['ins_time'] < (time() - (30*24*3600))){ // Go Pro link add_submenu_page('loginizer', __('Loginizer Go Pro', 'loginizer'), __('Go Pro', 'loginizer'), 'activate_plugins', LOGINIZER_PRO_URL); } // NOTE: This hook is just for Social Login for now, // will need to change if we make different CSS files for every Menu page // INFO:: Using this action helps is loading the CSS beforehand preventing any layout change on load. add_action('load-'.$hook_name, 'loginizer_load_admin_assets'); } // Loads assets like CSS and JS for admin Pages. function loginizer_load_admin_assets(){ wp_enqueue_style('loginizer_social_style', LOGINIZER_URL . '/assets/css/social-admin.css', [], LOGINIZER_VERSION); wp_enqueue_script('loginizer_social_script', LOGINIZER_URL . '/assets/js/social-admin.js', ['jquery', 'jquery-ui-sortable'], LOGINIZER_VERSION, true); } // Show the promo function loginizer_promo(){ echo '
Dismiss

We are glad you like Loginizer and have been using it since the past few days. It is time to take the next step

Upgrade to Pro Rate it 5★\'s Like Us on Facebook Tweet about Loginizer

'; } function loginizer_recaptcha_settings(){ include_once LOGINIZER_DIR . '/main/settings/recaptcha.php'; loginizer_page_recaptcha(); } function loginizer_2fa_settings(){ include_once LOGINIZER_DIR . '/main/settings/2fa.php'; loginizer_page_2fa(); } function loginizer_passwordless_settings(){ include_once LOGINIZER_DIR . '/main/settings/passwordless.php'; loginizer_page_passwordless(); } function loginizer_security_settings(){ include_once LOGINIZER_DIR . '/main/settings/security.php'; loginizer_page_security(); } function loginizer_brute_force_settings(){ include_once LOGINIZER_DIR . '/main/settings/brute-force.php'; loginizer_page_brute_force(); } function loginizer_checksums_settings(){ include_once LOGINIZER_DIR . '/main/settings/checksum.php'; loginizer_page_checksums(); } function loginizer_dashboard(){ include_once LOGINIZER_DIR . '/main/settings/dashboard.php'; loginizer_page_dashboard(); } function loginizer_sso_settings(){ include_once LOGINIZER_DIR . '/main/settings/sso.php'; loginizer_sso(); } function loginizer_social_login_settings(){ include_once LOGINIZER_DIR . '/main/settings/social_login.php'; loginizer_social_login(); } // Hides the interim login poup after successful login. function loginizer_social_interim_js(){ if(isset($_GET['interim_login']) && $_GET['interim_login'] === 'lz' && is_user_logged_in()){ echo ''; } } // Show alert when the login url gets changes from when the Loginizer social settings were setup. function loginizer_social_login_url_alert(){ // We want to show this error to user which has sufficient privilage if(!current_user_can('activate_plugins')){ return; } if(get_option('loginizer_social_login_url', '') === wp_login_url()){ return; } $provider_settings = get_option('loginizer_provider_settings', []); // If we dont have any settings then it dosent matter what URL is set. // As it will be updated once user saves the settings. if(empty($provider_settings)){ return; } $has_enabled = false; foreach($provider_settings as $provider){ if($provider['enabled']){ $has_enabled = true; break; } } // If we have no Provider enabled then just show the warning on the social login page. if(empty($has_enabled) && (empty($_GET['page']) || $_GET['page'] !== 'loginizer_social_login')){ return; } echo '

'.esc_html__('Your changed login slug!', 'loginizer').'

'.esc_html__('You changed the login slug and have some social login apps enabled. These social login apps use the login URL as the redirect URI. This means that since the login URL has changed, the social login will now break.', 'loginizer').'

'.esc_html__('How to fix:', 'loginizer').'

'.esc_html__('When you created secret keys for the social apps, you also provided a Redirect URI. To fix this issue, you need to update that Redirect URI.', 'loginizer').'
You just need to update the /wp-login.php to the new slug you have.

'; wp_register_script('loginizer_social_alert', '', ['jquery'], LOGINIZER_VERSION, true); wp_enqueue_script('loginizer_social_alert'); wp_add_inline_script('loginizer_social_alert', ' jQuery("#loginizer-social-login-alert").on("click", function(){ jQuery(this).closest(".notice").slideToggle(); var data = new Object(); data["action"] = "loginizer_dismiss_social_alert"; data["nonce"] = "'.wp_create_nonce('loginizer_admin_ajax').'"; var admin_url = "'.admin_url().'"+"admin-ajax.php"; jQuery.post(admin_url, data, function(response){ }); });'); } function loginizer_softwp_upgrader_notice(){ // We want to show this error to user which has sufficient privilage if(!current_user_can('activate_plugins')){ return; } /*$notice_end_time = strtotime('31 March 2025'); if(!empty($notice_end_time) && time() > $notice_end_time){ return; }*/ $softwp_upgrade = get_option('loginizer_softwp_upgrade', 0); if(empty($softwp_upgrade) || $softwp_upgrade < 0){ return; } if(empty($_GET['page']) || !preg_match('/loginizer/is', $_GET['page'])){ return; } echo '
'.esc_html__('Dismiss Forever', 'loginizer').'

' . esc_html__('Hey, you might be eligible to access Premium plugins provided by Softaculous for Free ! Please refer to these documents for details on how to avail them.', 'loginizer').' ' . esc_html__('Generate SoftWP License', 'loginizer') . ' | ' . esc_html__('How to Install the Pro plugins', 'loginizer') . '

'; wp_register_script('loginizer_softwp_alert', '', ['jquery'], LOGINIZER_VERSION, true); wp_enqueue_script('loginizer_softwp_alert'); wp_add_inline_script('loginizer_softwp_alert', ' jQuery("#loginizer-softwp-promo-close").on("click", function(){ jQuery(this).closest("#loginizer_softwp_notice").slideToggle(); var data = new Object(); data["action"] = "loginizer_dismiss_softwp_alert"; data["security"] = "'.wp_create_nonce('loginizer_softwp_notice').'"; var admin_url = "'.admin_url().'"+"admin-ajax.php"; jQuery.post(admin_url, data, function(response){ }); });'); } function loginizer_check_expires(){ global $loginizer; // We do not want to show the expiry notice if the license is by SoftWP. if(!empty($loginizer['license']) && !empty($loginizer['license']['has_plid'])){ return; } $current_timestamp = time(); $expiration_timestamp = strtotime($loginizer['license']['expires']); $time_diff = $expiration_timestamp - $current_timestamp; // Renew link $loginizer_user_license = $loginizer['license']['license']; $loginizer_user_plan = $loginizer['license']['plan']; $loginizer_renew_url = 'https://www.softaculous.com/clients?ca=loginizer_buy&plan=' . $loginizer_user_plan . '&license=' . $loginizer_user_license; if($time_diff > 0 && $time_diff <= WEEK_IN_SECONDS){ $human_time = human_time_diff($current_timestamp, $expiration_timestamp); echo '
'.esc_html__('Dismiss for 60 days', 'loginizer').'

' . sprintf(esc_html__('Alert : Your Loginizer Premium license will expire in %s. Renew to keep getting updates!', 'loginizer'), esc_html($human_time)).' ' . esc_html__('Click here to renew', 'loginizer') . '

'; } else if($time_diff <= 0){ echo '
'.esc_html__('Dismiss for 60 days', 'loginizer').'

' . esc_html__('Alert: Your Loginizer Premium license has expired. Please renew immediately to keep getting updates ', 'loginizer').' ' . esc_html__('Click here to renew', 'loginizer') . '

'; } wp_register_script('loginizer_license_alert', '', ['jquery'], LOGINIZER_VERSION, true); wp_enqueue_script('loginizer_license_alert'); wp_add_inline_script('loginizer_license_alert', ' jQuery("#loginizer-license-promo-close").on("click", function(){ jQuery(this).closest("#loginizer_license_notice").slideToggle(); var data = new Object(); data["action"] = "loginizer_dismiss_license_alert"; data["security"] = "'.wp_create_nonce('loginizer_license_notice').'"; var admin_url = "'.admin_url().'"+"admin-ajax.php"; jQuery.post(admin_url, data, function(response){ }); });'); } main/social-login.php000064400000041056147577535410010611 0ustar00get('social_security'))){ $lz_social_nonce = sanitize_text_field(self::$storage->get('social_security')); } if(!wp_verify_nonce($lz_social_nonce, 'loginizer_social_check')){ self::$error['security-check'] = __('Security check failed when trying to login', 'loginizer'); self::trigger_error(); return; } $providers = self::build_provider_arr(); if(empty($providers)){ self::$error['login_error'] = __('No Provider is configured, please contact the admin about this issue', 'loginizer'); self::trigger_error(); return; } $callback_query = []; $callback_query['lz_social_provider'] = lz_optget('lz_social_provider'); try { $config = [ // Location where to redirect users once they authenticate with a provider 'callback' => wp_login_url().'?'.http_build_query($callback_query), // Providers specifics 'providers' => $providers ]; $hybridauth = new Hybridauth($config); //Step 1: Here we will be redirected to the App Auth page if(!empty($_GET['lz_social_provider'])){ if(is_array($hybridauth->getProviders()) && in_array($_GET['lz_social_provider'], $hybridauth->getProviders())) { // Store the provider for the callback event self::$storage->set('provider', lz_optget('lz_social_provider')); if(!empty($_REQUEST['test'])){ self::$storage->set('test', true); } self::$storage->set('social_security', wp_create_nonce('loginizer_social_check')); if(!empty($_REQUEST['ref']) && wp_http_validate_url(sanitize_url(wp_unslash($_REQUEST['ref'])))){ self::$storage->set('ref', rawurlencode(sanitize_url(wp_unslash($_REQUEST['ref'])))); } if(isset($_REQUEST['interim-login'])){ self::$storage->set('interim_login', 'lz'); } } else { self::$error['provider_error'] = esc_html__('The app you are trying to login through is not configured', 'loginizer'); self::trigger_error(); return; } } // Step 2: After we are back from the Apps auth page. if($provider = self::$storage->get('provider')){ if(!is_array($hybridauth->getProviders()) || !in_array($provider, $hybridauth->getProviders())) { self::$error['provider_error'] = esc_html__('The app you are trying to login through is not configured', 'loginizer'); self::trigger_error(); return; } $hybridauth->authenticate($provider); self::$storage->set('provider', null); // Cleaning self::$storage->delete('social_security'); self::$provider = $provider; if(self::$storage->get('test')){ self::$storage->delete('test'); self::$test = true; } if(self::$storage->get('ref')){ self::$ref = rawurldecode(self::$storage->get('ref')); self::$storage->delete('ref'); } if(self::$storage->get('interim_login')){ self::$interim_login = self::$storage->get('interim_login'); self::$storage->delete('interim_login'); } // Retrieve the provider record $adapter = $hybridauth->getAdapter($provider); $userProfile = $adapter->getUserProfile(); $accessToken = $adapter->getAccessToken(); // Check if the user have account which is verified if(empty($userProfile->emailVerified)){ self::$error['login_failed'] = __('The social account you are using does not have a verified email.', 'loginizer'); $adapter->disconnect(); self::trigger_error(); return; } $data = [ 'access_token' => $accessToken, 'identifier' => $userProfile->identifier, 'email' => $userProfile->email, 'first_name' => $userProfile->firstName, 'last_name' => $userProfile->lastName, 'photoURL' => strtok($userProfile->photoURL, '?'), ]; $adapter->disconnect(); if(empty($data['email'])){ self::$error['login_failed'] = __('No email details were returned !', 'loginizer'); self::trigger_error(); return; } // If it is a test then, we are satisfied that the Provider is returning data // As this verifies the provider is working. if(self::$test === true){ self::close_tab(); return; } // Create an account if it does not exists. if(empty(email_exists(sanitize_email($data['email'])))){ if(self::$test === true){ self::$error['test_error'] = __('The email you are using for the test is not registered on this website. So register this email first.', 'loginizer'); self::trigger_error(); return; } if(defined('LOGINIZER_PREMIUM') && !empty($loginizer['social_settings']['general']['register_new'])){ self::register_account($data); echo 'Register Acount'; return; } self::$error['login_error'] = __('You can not register through Social Login', 'loginizer'); self::trigger_error(); return; } $user = get_user_by('email', sanitize_email($data['email'])); if(empty($user)){ self::$error['login_error'] = __('User with this email does not exists.', 'loginizer'); self::trigger_error(); return; } $authenticated = loginizer_wp_authenticate($user, $user->user_login, $user->user_pass); if(is_wp_error($authenticated)){ return; } self::login_user($user); self::close_tab(); } }catch(\Exception $e){ @error_log('Loginizer Logs(Social): '. esc_html($e->getMessage())); self::$error['login_error'] = __('Oops, we ran into an issue! ', 'loginizer') . $e->getMessage(); self::trigger_error(); //wp_safe_redirect(wp_login_url()); return; } } private static function login_user($user, $username = '', $password = ''){ if(isset($user) && is_object($user) && property_exists($user, 'ID') && empty(self::$test)){ clean_user_cache(get_current_user_id()); clean_user_cache($user->ID); wp_clear_auth_cookie(); wp_set_current_user($user->ID, $user->user_login); wp_set_auth_cookie($user->ID, true, is_ssl()); do_action('wp_login', $user->user_login, $user); update_user_caches($user); return true; } } /** * Creates a User account * * @param mixed[] $data Data we get from the Social App * @return void */ static function register_account($data){ global $loginizer; $username = $data['first_name'] . $data['last_name']; if(empty($username)){ $parsed_email = explode('@', $data['email']); if(!empty($parsed_email[0])){ $username = preg_replace('/[^A-Za-z0-9\-]/', '', $parsed_email[0]); } } $username = str_replace(' ', '', strtolower($username)); $username = sanitize_user($username, true); $i = 1; while(username_exists($username)){ $username .= $i; $i++; } $password = wp_generate_password(12); $userdata = [ 'user_login' => sanitize_text_field($username), 'user_pass' => $password, 'user_email' => sanitize_email($data['email']), 'role' => (!empty($loginizer['social_settings']['general']['default_role']) ? sanitize_text_field($loginizer['social_settings']['general']['default_role']) : 'subscriber'), 'show_admin_bar_front' => (!empty($loginizer['social_settings']['general']['hide_admin_bar']) ? false : true), ]; $user_id = wp_insert_user($userdata); // TODO: Handle Error here. if(is_wp_error($user_id)){ self::$error['registration_failed'] = __('Something went wrong while creating the user', 'loginizer'). $user_id->get_error_message(); return; } if(empty($user_id)){ self::$error['registration_failed'] = __('Unable to register your account, try again later!', 'loginizer'); self::close_tab(); return; } update_user_option($user_id, 'default_password_nag', true, true); // This will show alert to user to change the password. $user = get_user_by('ID', $user_id); // Save avatar if possible. $tried_to_download = get_user_meta($user->ID, 'loginizer_avatar_download', true); if(!empty($data['photoURL']) && !empty($loginizer['social_settings']['general']['save_avatar']) && empty($tried_to_download)){ self::save_avatar($data['photoURL'], $user->ID); } // Logging In the new user. self::login_user($user); // Closing the tab and redirecting to the admin. $redirect_to = admin_url(); self::close_tab(); } /** * Creates an array of Config which is valid for HybridAuth using the setting of the provider. * * @return mixed[] */ private static function build_provider_arr(){ $config = []; $providers = get_option('loginizer_provider_settings', []); if(empty($providers)){ return $config; } foreach($providers as $key => $provider){ if(empty($provider['enabled']) || empty($provider['client_id']) || empty($provider['client_secret'])){ continue; } $config_index = ucfirst($key); $config[$config_index] = [ 'enabled' => true, 'keys' => [ 'id' => $provider['client_id'], 'secret' => $provider['client_secret'] ] ]; } return $config; } /** * Close the Tab or redirects back to the Login Page. * * @param string $redirect_to URL where the user should be redirected, leave empty if want to redirect to admin. * @return void */ protected static function close_tab(){ global $loginizer; // Check if the URL is safe to use. if(!empty(self::$ref)){ $redirect_to = self::handle_redirect(self::$ref); } $target_window = 'same'; // If to redirect or to close the poup $is_interim = ''; // If interim add query string as a identifier if(self::$interim_login == 'lz'){ $target_window = 'popup'; $is_interim = '?interim_login=lz'; } else if(!empty(self::$test)){ $target_window = 'popup'; $redirect_to .= '&provider='.self::$provider.'&test=1'; }else if(!empty($loginizer['social_settings']['general']['target_window'])){ $target_window = $loginizer['social_settings']['general']['target_window']; } if(empty($redirect_to) || $redirect_to == admin_url()){ $redirect_to = admin_url($is_interim); } if($target_window === 'same'){ wp_safe_redirect($redirect_to); die(); } if(isset(self::$interim_login) && self::$interim_login === 'lz' && is_user_logged_in()){ echo esc_html__('Login Successful', 'loginizer'); } echo ''; die(); } // Download the avatar and returns Image ID protected static function save_avatar($url, $user_id){ update_user_meta($user_id, 'loginizer_avatar_download', true); $tmp_file = self::download_avatar($url); if(is_wp_error($tmp_file) || empty($tmp_file)){ return $tmp_file; } $mime = wp_get_image_mime($tmp_file); $allowed_mime = [ 'image/webp' => 'webp', 'image/tiff' => 'tif', 'image/gif' => 'gif', 'image/jpeg' => 'jpg', 'image/bmp' => 'bmp', 'image/png' => 'png', ]; if(!array_key_exists($mime, $allowed_mime)){ error_log('Loginizer Error: ' . __('The avatar has unsupported mime type.', 'loginizer')); return; } $upload_dir = wp_upload_dir(); $avatar_upload_dir = trailingslashit($upload_dir['basedir']) . 'lz_avatars'; if(!wp_mkdir_p($avatar_upload_dir)){ error_log('Loginizer Error: ' . __('Unable to create Directory to save avatars', 'loginizer')); return; } $avatar_file = wp_hash($user_id) .'.'. $allowed_mime[$mime]; $avatar_file = wp_unique_filename($avatar_upload_dir, $avatar_file); $avatar_file_path = trailingslashit($avatar_upload_dir) . $avatar_file; $new_file = copy($tmp_file, $avatar_file_path); unlink($tmp_file); if(empty($new_file)){ error_log('Loginizer Error: ' . __('Unable to copy the avatar from the tmp file', 'loginizer')); return; } $avatar_url = $upload_dir['baseurl'] . '/lz_avatars/' . basename($avatar_file); $attachment = [ 'guid' => $avatar_url, 'post_title' => '', 'post_content' => '', 'post_author' => $user_id, 'post_status' => 'private', 'post_mime_type' => $mime, ]; $attachment_id = wp_insert_attachment($attachment, $avatar_file_path); if(is_wp_error($attachment_id)){ unlink($avatar_file_path); error_log('Loginizer Error: ' . __('Unable to create an attachment of the Avatar', 'loginizer')); return; } global $wpdb, $blog_id; include_once(ABSPATH . 'wp-admin/includes/image.php'); wp_update_attachment_metadata($attachment_id, wp_generate_attachment_metadata($attachment_id, $avatar_file_path)); update_post_meta($attachment_id, '_wp_attachment_wp_user_avatar', $user_id); update_user_meta($user_id, $wpdb->get_blog_prefix($blog_id) . 'lz_avatar', $attachment_id); } private static function download_avatar($url){ if(empty($url)){ error_log('Loginizer Error: ' . __('The URL provided to download avatar is empty', 'loginizer')); return; } $tmp_file = uniqid(); if(empty($tmp_file)){ error_log('Loginizer Error: ' . __('Unable to create a tmp file!', 'loginizer')); return; } $response = wp_remote_get($url, [ 'timeout' => 30, 'stream' => true, 'filename' => $tmp_file, ]); if(is_wp_error($response)){ unlink($tmp_file); error_log('Loginizer Error: ' . __('Download of the avatar failed!', 'loginizer')); return; } $code = wp_remote_retrieve_response_code($response); if($code != 200){ unlink($tmp_file); error_log('Loginizer Error: ' . sprintf(__('Download of the avatar failed with error code %s!', 'loginizer'), esc_html($code))); return; } $content_md5 = wp_remote_retrieve_header($response, 'content-md5'); if(!empty($content_md5)){ if(!function_exists('verify_file_md5')){ include_once ABSPATH . 'wp-admin/includes/file.php'; } $md5_check = verify_file_md5($tmp_file, $content_md5); if(is_wp_error($md5_check)){ unlink($tmpfname); return $md5_check; } } return $tmp_file; } protected static function handle_redirect($url){ $redirect = ''; if(empty($url)){ return $redirect; } $url = rawurldecode($url); $parsed_url = parse_url($url); // If we have something in redirect to, then redirect to that page if(!empty($parsed_url['query'])){ preg_match('/(redirect_to|redirect)=([^&]*)/', $parsed_url['query'], $redirect_url); if(!empty($redirect_url[2])){ return rawurldecode($redirect_url[2]); } } // Reloading the page wont show the admin page so we need to redirect it to the admin page. if($parsed_url['scheme'].'://'.$parsed_url['host'] . $parsed_url['path'] == wp_login_url()){ return $redirect; } if(strpos(wp_login_url(), $parsed_url['path']) !== FALSE){ return $redirect; } // If none of the above happens then we will just make the page reload. return $url; } static function trigger_error(){ if(empty(self::$error)){ return; } // If we are testing we can just die, // becuase we don't want the user to be redirected anywhere if(!empty(self::$test) || (!empty(self::$storage) && self::$storage->get('test'))){ wp_die(wp_kses_post(current(self::$error))); } do_action('wp_login_failed', ''); self::error_state(); self::close_tab(); // This will redirect to the appropriate page. } // Stores the errors to be used once redirected. static function error_state(){ global $loginizer; $data = [ 'errors' => self::$error, 'retries_left' => $loginizer['retries_left'] ]; $identifier = uniqid('lz_social', true); set_site_transient($identifier, $data, 300); setcookie('lz_social_error', $identifier, time() + 300, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true); } } Loginizer_Social_Login::login_init();loginizer.php000064400000004415147577535410007305 0ustar00. */ if(!function_exists('add_action')){ echo 'You are not allowed to access this page directly.'; exit; } $_tmp_plugins = get_option('active_plugins', []); if(!defined('SITEPAD') && in_array('loginizer-security/loginizer-security.php', $_tmp_plugins)){ // Was introduced in 1.9.0 $loginizer_pro_info = get_option('loginizer_pro_version'); if(!empty($loginizer_pro_info) && version_compare($loginizer_pro_info, '1.9.0', '>=')){ // Let Loginizer load // Lets check for older versions }else{ if(!function_exists('get_plugin_data')){ include_once ABSPATH . 'wp-admin/includes/plugin.php'; } $loginizer_pro_info = get_plugin_data(WP_PLUGIN_DIR . '/loginizer-security/loginizer-security.php'); if(!empty($loginizer_pro_info) && version_compare($loginizer_pro_info['Version'], '1.8.9', '<')){ return; } } } function loginizer_load_plugin_textdomain(){ load_plugin_textdomain('loginizer', FALSE, basename( dirname( __FILE__ ) ) . '/languages/'); } add_action('init', 'loginizer_load_plugin_textdomain', 0); // Is the premium plugin active ? if(defined('LOGINIZER_VERSION')){ return; } define('LOGINIZER_FILE', __FILE__); include_once(dirname(__FILE__).'/init.php'); functions.php000064400000053127147577535410007317 0ustar00get_results($query, 'ARRAY_A'); if(empty($array)){ return current($result); }else{ return $result; } } // Check if an IP is valid function lz_valid_ip($ip){ // IPv6 if(lz_valid_ipv6($ip)){ return true; } // IPv4 if(!ip2long($ip) || !lz_valid_ipv4($ip)){ return false; } return true; } function lz_valid_ipv4($ip){ if(!preg_match('/^(\d){1,3}\.(\d){1,3}\.(\d){1,3}\.(\d){1,3}$/is', $ip) || substr_count($ip, '.') != 3){ return false; } $r = explode('.', $ip); foreach($r as $v){ $v = (int) $v; if($v > 255 || $v < 0){ return false; } } return true; } function lz_valid_ipv6($ip){ $pattern = '/^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/'; if(!preg_match($pattern, $ip)){ return false; } return true; } // Check if a field is posted via POST else return default value function lz_optpost($name, $default = ''){ if(!empty($_POST[$name])){ return lz_inputsec(lz_htmlizer(trim($_POST[$name]))); } return $default; } // Check if a field is posted via GET else return default value function lz_optget($name, $default = ''){ if(!empty($_GET[$name])){ return lz_inputsec(lz_htmlizer(trim($_GET[$name]))); } return $default; } // Check if a field is posted via GET or POST else return default value function lz_optreq($name, $default = ''){ if(!empty($_REQUEST[$name])){ return lz_inputsec(lz_htmlizer(trim($_REQUEST[$name]))); } return $default; } // For filling in posted values function lz_POSTval($name, $default = ''){ return (!empty($_POST) ? (!isset($_POST[$name]) ? '' : esc_html($_POST[$name])) : $default); } function lz_POSTchecked($name, $default = false, $submit_name = ''){ if(!empty($submit_name)){ $post_to_check = isset($_POST[$submit_name]) ? $_POST[$submit_name] : ''; }else{ $post_to_check = $_POST; } return (!empty($post_to_check) ? (isset($_POST[$name]) ? 'checked="checked"' : '') : (!empty($default) ? 'checked="checked"' : '')); } function lz_POSTselect($name, $value, $default = false){ if(empty($_POST)){ if(!empty($default)){ return 'selected="selected"'; } }else{ if(isset($_POST[$name])){ if(trim($_POST[$name]) == $value){ return 'selected="selected"'; } } } } function lz_POSTradio($name, $val, $default = null){ return (!empty($_POST) ? (@$_POST[$name] == $val ? 'checked="checked"' : '') : (!is_null($default) && $default == $val ? 'checked="checked"' : '')); } function lz_inputsec($string){ $string = addslashes($string); // This is to replace ` which can cause the command to be executed in exec() $string = str_replace('`', '\`', $string); return $string; } function lz_htmlizer($string){ $string = htmlentities($string, ENT_QUOTES, 'UTF-8'); preg_match_all('/(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)/', $string, $matches);//r_print($matches); foreach($matches[1] as $mk => $mv){ $tmp_m = lz_entity_check($matches[2][$mk]); $string = str_replace($matches[1][$mk], $tmp_m, $string); } return $string; } function lz_entity_check($string){ //Convert Hexadecimal to Decimal $num = ((substr($string, 0, 1) === 'x') ? hexdec(substr($string, 1)) : (int) $string); //Squares and Spaces - return nothing $string = (($num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num < 0x20) ? '' : '&#'.$num.';'); return $string; } // Check if a checkbox is selected function lz_is_checked($post){ if(!empty($_POST[$post])){ return true; } return false; } // Reoort an error function lz_report_error($error = array()){ if(empty($error)){ return true; } $error_string = 'Please fix the below error(s) :
'; foreach($error as $ek => $ev){ $error_string .= '* '.$ev.'
'; } echo '

' . __($error_string, 'loginizer') . '

'; } // Report a notice function lz_report_notice($notice = array()){ global $wp_version; if(empty($notice)){ return true; } // Which class do we have to use ? if(version_compare($wp_version, '3.8', '<')){ $notice_class = 'updated'; }else{ $notice_class = 'updated'; } $notice_string = 'Please check the below notice(s) :
'; foreach($notice as $ek => $ev){ $notice_string .= '* '.$ev.'
'; } echo '

' . __($notice_string, 'loginizer') . '

'; } // Convert an objext to array function lz_objectToArray($d){ if(is_object($d)){ $d = get_object_vars($d); } if(is_array($d)){ return array_map(__FUNCTION__, $d); // recursive }elseif(is_object($d)){ return lz_objectToArray($d); }else{ return $d; } } // Sanitize variables function lz_sanitize_variables($variables = array()){ if(is_array($variables)){ foreach($variables as $k => $v){ $variables[$k] = trim($v); $variables[$k] = escapeshellcmd($v); } }else{ $variables = escapeshellcmd(trim($variables)); } return $variables; } // Is multisite ? function lz_is_multisite() { if(function_exists('get_site_option') && function_exists('is_multisite') && is_multisite()){ return true; } return false; } // Generate a random string function lz_RandomString($length = 10){ $characters = '0123456789abcdefghijklmnopqrstuvwxyz'; $charactersLength = strlen($characters); $randomString = ''; for($i = 0; $i < $length; $i++){ $randomString .= $characters[rand(0, $charactersLength - 1)]; } return $randomString; } function lz_print($array){ echo '
';
	print_r($array);
	echo '
'; } function lz_cleanpath($path){ $path = str_replace('\\\\', '/', $path); $path = str_replace('\\', '/', $path); $path = str_replace('//', '/', $path); return rtrim($path, '/'); } // Returns the Numeric Value of results Per Page function lz_get_page($get = 'page', $resperpage = 50){ $resperpage = (!empty($_REQUEST['reslen']) && is_numeric($_REQUEST['reslen']) ? (int) lz_optreq('reslen') : $resperpage); if(lz_optget($get)){ $pg = (int) lz_optget($get); $pg = $pg - 1; $page = ($pg * $resperpage); $page = ($page <= 0 ? 0 : $page); }else{ $page = 0; } return $page; } // Replaces the Variables with the supplied ones function lz_lang_vars_name($str, $array){ foreach($array as $k => $v){ $str = str_replace('$'.$k, $v, $str); } return $str; } // Check if Loginizer is premium function loginizer_is_premium(){ return defined('LOGINIZER_PREMIUM'); } function loginizer_feature_available($feature = '', $return = 0){ if(loginizer_is_premium()){ return true; } $msg = ''; if(!empty($feature)){ $msg .= ''.$feature.' is available in the Pro version of Loginizer. '; } $msg .= 'Upgrade to Pro'; if(!empty($return)){ return $msg; }else{ echo '
'.$msg.'
'; } } // Checks if the email is valid function lz_valid_email($email){ return filter_var($email, FILTER_VALIDATE_EMAIL); } function loginizer_blocked_page($lz_error){ // We are checking if this is a login page or not if(false === stripos(wp_login_url(), $_SERVER['REQUEST_URI'])){ return; } echo ' '.__('You can not access this page', 'loginizer').'

'.__('You can not access this page', 'loginizer').'

'.wp_kses_post(implode('', $lz_error)).'

'; if(!defined('LOGINIZER_PREMIUM')){ echo '

Powered by Loginizer

'; } else { echo '

Powered by ' . esc_html(get_bloginfo('name')) . '

'; } echo '
'; die(); } function loginizer_social_css($position = 'below'){ global $loginizer; // Starting the CSS $css = '#lz-social-login-btns{display:none;max-width:350px;width:100%;box-sizing:border-box}.lz-social-login-btns-icon{flex-direction:row;flex-wrap:wrap;gap:8px}.lz-social-login-btns-full{flex-wrap:wrap;}.lz-social-top-padding{padding-top:20px}.lz-social-bottom-padding{padding-bottom:20px}.loginizer-social-button{display:flex;align-items:center;cursor:pointer;margin-bottom:10px;box-sizing:border-box;text-align:center;line-height:1;border:none;outline:none}.lz-social-button-icon{padding:0;width:40px;height:40px}.lz-social-button-icon-circle{border-radius:20px}.lz-social-button-icon-square{border-radius:3px}.lz-social-button-btn{padding:0 12px;width:100%;max-width:400px}.loginizer-social-button img{border-radius:2px;max-width:24px !important;}.loginizer-social-btn-logo{display:flex;padding:8px;align-items:center}.loginizer-social-btn-text{text-align:center;margin-left:15px;padding:10px 0;font-size:16px}'; // CSS for the divider with the text OR in the middle if(!empty($loginizer['social_settings']) && ($position === 'below' || $position === 'above')){ $css .= '.loginizer-social-divider{flex: 1 0 100%; font-size:17px; font-weight:700; margin-bottom:10px; display:flex;align-items:center}.loginizer-social-divider::after,.loginizer-social-divider::before{flex:1;content:"";padding:1px;background-color:#ececec;margin:5px}'; } wp_register_style('loginizer-social', '', [], LOGINIZER_VERSION); wp_enqueue_style('loginizer-social'); wp_add_inline_style('loginizer-social', $css); } // Echos the HTML of the Social button function loginizer_social_btn($return = false, $page_type = 'login', $short_atts = []){ global $loginizer, $loginizer_login_providers; // We don't want to give social login option to blacklisted IP's if(loginizer_is_blacklisted() || !loginizer_can_login()){ return; } $provider_settings = get_option('loginizer_provider_settings', []); $provider_order = get_option('loginizer_social_order', []); // in what order the icons are to be shown $providers = []; // Sorting according to the saved order of the providers. if(!empty($provider_order)){ foreach($provider_order as $key => $num){ if(!empty($provider_settings[$key])){ $providers[$key] = $provider_settings[$key]; } } } else { $providers = $provider_settings; } if(empty($providers)){ return; } // ------ HTML STARTS HERE -------- // $style = 'full'; $shape = 'square'; $position = 'below'; if(!empty($loginizer['social_settings'])){ $social_settings = $loginizer['social_settings']; // Setting Button Style if(!empty($social_settings[$page_type]['button_style']) && $social_settings[$page_type]['button_style'] === 'icon'){ $style = 'icon'; } // Setting Button Shape if(!empty($social_settings[$page_type]['button_shape']) && $social_settings[$page_type]['button_shape'] !== 'square'){ $shape = $social_settings[$page_type]['button_shape']; } // Setting position if(!empty($social_settings[$page_type]['button_position']) && $social_settings[$page_type]['button_position'] !== 'below'){ $position = $social_settings[$page_type]['button_position']; } } // Shortcode style if(!empty($short_atts['type'])){ $style = esc_html($short_atts['type']); } // Shortcode Shape square or circle if(!empty($short_atts['shape'])){ $shape = esc_html($short_atts['shape']); } // Setting divider position $divider_pos = ''; if(!empty($short_atts['divider']) && $short_atts['divider'] == 'above'){ $divider_pos = 'above'; }elseif(empty($short_atts) && !empty($loginizer['social_settings']) && $position === 'below_plus'){ $divider_pos = 'above'; }elseif(!empty($short_atts['divider']) && $short_atts['divider'] == 'below'){ $divider_pos = 'below'; }elseif(empty($short_atts) && !empty($loginizer['social_settings']) && $position === 'above_plus'){ $divider_pos = 'below'; } loginizer_social_css($divider_pos); $padding = [ 'above' => 'bottom', 'below' => 'top' ]; $social_buttons = ''; loginizer_add_social_js($page_type); if($return){ return $social_buttons; } echo wp_kses($social_buttons, [ 'div' => ['class' => true, 'id' => true, 'style' => true, 'onclick' => true], 'img' => ['src' => true, 'height' => true, 'alt' => true, 'width' => true], 'strong' => true, ]); } function loginizer_add_social_js($page_type){ global $loginizer; if(wp_script_is('loginizer-social-js') || loginizer_is_blacklisted() || !empty($loginizer['social_script_added'])){ return; } $loginizer['social_script_added'] = false; $func = 'append'; if(!empty($loginizer['social_settings']) && !empty($loginizer['social_settings'][$page_type]['button_position']) && strpos($loginizer['social_settings'][$page_type]['button_position'], 'above') !== FALSE){ $func = 'prepend'; } $target_window = 'same'; if(isset($_GET['interim-login']) && $_GET['interim-login'] == 1){ $target_window = 'popup'; } elseif(!empty($loginizer['social_settings']['general']['target_window'])){ $target_window = $loginizer['social_settings']['general']['target_window']; } $social_nonce = wp_create_nonce('loginizer_social_check'); wp_register_script('loginizer-social-js', '', ['jquery'], LOGINIZER_VERSION, ['strategy' => false, 'in_footer' => true]); wp_enqueue_script('loginizer-social-js'); wp_add_inline_script('loginizer-social-js', ' let lz_form = document.querySelectorAll("#loginform, #registerform, .woocommerce-form-login, .woocommerce-form-register, #front-login-form, #setupform"), lz_social_btns = document.querySelectorAll("#lz-social-login-btns"), lz_target_window = "'.esc_html($target_window).'", lz_is_interim = "'.(!empty($_GET['interim-login']) ? '&interim-login=0' : '').'"; // as for interim it should only open a popup lz_social_btns.forEach((btns) => { btns.style.display="flex"; }); if(lz_form.length > 0){ lz_form.forEach((form, index) => { lz_social_btns[index].style.display="flex"; form.'.esc_html($func).'(lz_social_btns[index]); }); } function loginizer_auth_popup(provider) { if(lz_target_window == "same"){ window.location.href = "'.esc_url(wp_login_url()).'?social_security='.esc_html($social_nonce).'&lz_social_provider="+ provider +"&ref='.esc_url($_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']).'"; return; } let screen_x = window.screenX !== undefined ? window.screenX : window.screenLeft, screen_y = window.screenY !== undefined ? window.screenY : window.screenTop, outer_width = window.outerWidth !== undefined ? window.outerWidth : document.documentElement.clientWidth, outer_height = window.outerHeight !== undefined ? window.outerHeight : document.documentElement.clientHeight - 22, target_width = 600, target_height = 600, right = parseInt(screen_y + (outerHeight - target_height) / 2.5, 10), left = parseInt(screen_x + (outerWidth - target_width) / 2, 10), attributes = []; if(target_width !== null){ attributes.push("width=" + target_width); } if(target_height !== null){ attributes.push("height=" + target_height); } attributes.push("left=" + left); attributes.push("top=" + right); attributes.push("scrollbars=1"); var auth_window = window.open("'.esc_url(wp_login_url()).'?social_security='.esc_html($social_nonce).'&lz_social_provider=" + provider+lz_is_interim+"&ref='.esc_url($_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']).'", "authWindow", attributes.join(",")); if(window.focus){ auth_window.focus(); } return false; }' ); $loginizer['social_script_added'] = true; } function loginizer_social_btn_login($return = false, $id = ''){ loginizer_social_btn($return, 'login'); } function loginizer_get_social_error(){ global $loginizer; // If we already have the error set if(!empty($loginizer['social_errors'])){ return; } if(empty($_COOKIE) || empty($_COOKIE['lz_social_error'])){ return; } $error_identifier = sanitize_text_field(wp_unslash($_COOKIE['lz_social_error'])); $social_errors = get_site_transient($error_identifier); if(empty($social_errors['errors'])){ return; } // Setting error data $loginizer['retries_left'] = $social_errors['retries_left']; $loginizer['social_errors'] = $social_errors['errors']; // Cleaning the error data delete_site_transient($error_identifier); // Deleting the data //setcookie('lz_social_error', '', time() - 300, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true); // have to delete }index.php000064400000000032147577535410006401 0ustar00prefix."loginizer_logs`"; $sql[] = "CREATE TABLE `".$wpdb->prefix."loginizer_logs` ( `username` varchar(255) NOT NULL DEFAULT '', `time` int(10) NOT NULL DEFAULT '0', `count` int(10) NOT NULL DEFAULT '0', `lockout` int(10) NOT NULL DEFAULT '0', `ip` varchar(255) NOT NULL DEFAULT '', `url` varchar(255) NOT NULL DEFAULT '', UNIQUE KEY `ip` (`ip`) ) DEFAULT CHARSET=utf8;"; foreach($sql as $sk => $sv){ $wpdb->query($sv); } add_option('loginizer_version', LOGINIZER_VERSION); add_option('loginizer_options', array()); add_option('loginizer_last_reset', 0); add_option('loginizer_whitelist', array()); add_option('loginizer_blacklist', array()); add_option('loginizer_2fa_whitelist', array()); // TODO:: REMOVE THIS AFTER MARCH 2025 $softwp_upgrade = get_option('loginizer_softwp_upgrade', 0); if(!defined('SITEPAD') && empty($softwp_upgrade)){ loginizer_check_softaculous(); } } /** * Updates the database structure for Loginizer * * If the plugin files are updated but database structure is not updated * this function will update the database structure as per the plugin version * NOTE: This does not update plugin files it just updates the database structure */ function loginizer_update_check(){ global $wpdb; $sql = array(); $current_version = get_option('loginizer_version'); // It must be the 1.0 pre stuff if(empty($current_version)){ $current_version = get_option('lz_version'); } $version = (int) str_replace('.', '', $current_version); // No update required if($current_version == LOGINIZER_VERSION){ return true; } // Is it first run ? if(empty($current_version)){ // Reinstall loginizer_activation(); // Trick the following if conditions to not run $version = (int) str_replace('.', '', LOGINIZER_VERSION); } // Is it less than 1.0.1 ? if($version < 101){ // TODO : GET the existing settings // Get the existing settings $lz_failed_logs = lz_selectquery("SELECT * FROM `".$wpdb->prefix."lz_failed_logs`;", 1); $lz_options = lz_selectquery("SELECT * FROM `".$wpdb->prefix."lz_options`;", 1); $lz_iprange = lz_selectquery("SELECT * FROM `".$wpdb->prefix."lz_iprange`;", 1); // Delete the three tables $sql = array(); $sql[] = "DROP TABLE IF EXISTS ".$wpdb->prefix."lz_failed_logs;"; $sql[] = "DROP TABLE IF EXISTS ".$wpdb->prefix."lz_options;"; $sql[] = "DROP TABLE IF EXISTS ".$wpdb->prefix."lz_iprange;"; foreach($sql as $sk => $sv){ $wpdb->query($sv); } // Delete option delete_option('lz_version'); // Reinstall loginizer_activation(); // TODO : Save the existing settings // Update the existing failed logs to new table if(is_array($lz_failed_logs)){ foreach($lz_failed_logs as $fk => $fv){ $insert_data = array('username' => $fv['username'], 'time' => $fv['time'], 'count' => $fv['count'], 'lockout' => $fv['lockout'], 'ip' => $fv['ip']); $format = array('%s','%d','%d','%d','%s'); $wpdb->insert($wpdb->prefix.'loginizer_logs', $insert_data, $format); } } // Update the existing options to new structure if(is_array($lz_options)){ foreach($lz_options as $ok => $ov){ if($ov['option_name'] == 'lz_last_reset'){ update_option('loginizer_last_reset', $ov['option_value']); continue; } $old_option[str_replace('lz_', '', $ov['option_name'])] = $ov['option_value']; } // Save the options update_option('loginizer_options', $old_option); } // Update the existing iprange to new structure if(is_array($lz_iprange)){ $old_blacklist = array(); $old_whitelist = array(); $bid = 1; $wid = 1; foreach($lz_iprange as $ik => $iv){ if(!empty($iv['blacklist'])){ $old_blacklist[$bid] = array(); $old_blacklist[$bid]['start'] = long2ip($iv['start']); $old_blacklist[$bid]['end'] = long2ip($iv['end']); $old_blacklist[$bid]['time'] = strtotime($iv['date']); $bid = $bid + 1; } if(!empty($iv['whitelist'])){ $old_whitelist[$wid] = array(); $old_whitelist[$wid]['start'] = long2ip($iv['start']); $old_whitelist[$wid]['end'] = long2ip($iv['end']); $old_whitelist[$wid]['time'] = strtotime($iv['date']); $wid = $wid + 1; } } if(!empty($old_blacklist)) update_option('loginizer_blacklist', $old_blacklist); if(!empty($old_whitelist)) update_option('loginizer_whitelist', $old_whitelist); } } // Is it less than 1.3.9 ? if($version < 139){ $wpdb->query("ALTER TABLE ".$wpdb->prefix."loginizer_logs ADD `url` VARCHAR(255) NOT NULL DEFAULT '' AFTER `ip`;"); } // Save the new Version update_option('loginizer_version', LOGINIZER_VERSION); // TODO:: REMOVE THIS AFTER MARCH 2025 $softwp_upgrade = get_option('loginizer_softwp_upgrade', 0); if(!defined('SITEPAD') && empty($softwp_upgrade)){ loginizer_check_softaculous(); } // In Sitepad Math Captcha is enabled by default if(defined('SITEPAD') && get_option('loginizer_captcha') === false){ $option['captcha_no_google'] = 1; add_option('loginizer_captcha', $option); } } // Add the action to load the plugin add_action('plugins_loaded', 'loginizer_load_plugin'); // The function that will be called when the plugin is loaded function loginizer_load_plugin(){ global $loginizer; // Check if the installed version is outdated loginizer_update_check(); // Set the array if(empty($loginizer)){ $loginizer = array(); } $loginizer['prefix'] = !defined('SITEPAD') ? 'Loginizer ' : 'SitePad '; $loginizer['app'] = !defined('SITEPAD') ? 'WordPress' : 'SitePad'; $loginizer['login_basename'] = !defined('SITEPAD') ? 'wp-login.php' : 'login.php'; $loginizer['wp-includes'] = !defined('SITEPAD') ? 'wp-includes' : 'site-inc'; // The IP Method to use $loginizer['ip_method'] = get_option('loginizer_ip_method'); if($loginizer['ip_method'] == 3){ $loginizer['custom_ip_method'] = get_option('loginizer_custom_ip_method'); } // Load settings $options = get_option('loginizer_options'); $loginizer['max_retries'] = empty($options['max_retries']) ? 3 : $options['max_retries']; $loginizer['lockout_time'] = empty($options['lockout_time']) ? 900 : $options['lockout_time']; // 15 minutes $loginizer['max_lockouts'] = empty($options['max_lockouts']) ? 5 : $options['max_lockouts']; $loginizer['lockouts_extend'] = empty($options['lockouts_extend']) ? 86400 : $options['lockouts_extend']; // 24 hours $loginizer['reset_retries'] = empty($options['reset_retries']) ? 86400 : $options['reset_retries']; // 24 hours $loginizer['notify_email'] = empty($options['notify_email']) ? 0 : $options['notify_email']; $loginizer['notify_email_address'] = lz_is_multisite() ? get_site_option('admin_email') : get_option('admin_email'); $loginizer['trusted_ips'] = empty($options['trusted_ips']) ? false : true; $loginizer['blocked_screen'] = empty($options['blocked_screen']) ? false : true; $loginizer['social_settings'] = get_option('loginizer_social_settings', []); if(!empty($options['notify_email_address'])){ $loginizer['notify_email_address'] = $options['notify_email_address']; $loginizer['custom_notify_email'] = 1; } // Login Success Email Notification. $loginizer['login_mail'] = get_option('loginizer_login_mail', []); add_action('init', 'loginizer_load_translation_vars', 0); $loginizer['login_mail_subject'] = empty($loginizer['login_mail']['subject']) ? '' : $loginizer['login_mail']['subject']; $loginizer['login_mail_body'] = empty($loginizer['login_mail']['body']) ? '' : $loginizer['login_mail']['body']; // Load the blacklist and whitelist $loginizer['blacklist'] = get_option('loginizer_blacklist', []); $loginizer['whitelist'] = get_option('loginizer_whitelist', []); $loginizer['2fa_whitelist'] = get_option('loginizer_2fa_whitelist'); // It should not be false if(empty($loginizer['2fa_whitelist'])){ $loginizer['2fa_whitelist'] = array(); } // When was the database cleared last time $loginizer['last_reset'] = get_option('loginizer_last_reset'); //print_r($loginizer); // Clear retries if((time() - $loginizer['last_reset']) >= $loginizer['reset_retries']){ loginizer_reset_retries(); } $ins_time = get_option('loginizer_ins_time'); if(empty($ins_time)){ $ins_time = time(); update_option('loginizer_ins_time', $ins_time); } $loginizer['ins_time'] = $ins_time; // Set the current IP $loginizer['current_ip'] = lz_getip(); // Is Brute Force Disabled ? $loginizer['disable_brute'] = get_option('loginizer_disable_brute'); // Filters and actions if(empty($loginizer['disable_brute'])){ // Use this to verify before WP tries to login // Is always called and is the first function to be called //add_action('wp_authenticate', 'loginizer_wp_authenticate', 10, 2);// Not called by XML-RPC add_filter('authenticate', 'loginizer_wp_authenticate', 10001, 3);// This one is called by xmlrpc as well as GUI // Is called when a login attempt fails // Hence Update our records that the login failed add_action('wp_login_failed', 'loginizer_login_failed'); // Is called before displaying the error message so that we dont show that the username is wrong or the password // Update Error message add_action('wp_login_errors', 'loginizer_error_handler', 10001, 2); add_action('woocommerce_login_failed', 'loginizer_woocommerce_error_handler', 10001); add_action('wp_login', 'loginizer_login_success', 10, 2); if(!empty($_COOKIE['lz_social_error']) && !empty($loginizer['social_settings']) && !loginizer_is_blacklisted()){ add_filter('wp_login_errors', 'loginizer_social_login_error_handler', 10000, 2); } } // Social Login Form Actions if(!empty($loginizer['social_settings']) && !loginizer_is_blacklisted()){ if(!empty($loginizer['social_settings']['login']['login_form'])){ add_action('login_form', 'loginizer_social_btn_login'); } } if((function_exists('wp_doing_ajax') && wp_doing_ajax()) || (defined( 'DOING_AJAX' ) && DOING_AJAX)){ include_once LOGINIZER_DIR . '/main/ajax.php'; } if(is_admin()){ include_once LOGINIZER_DIR . '/main/admin.php'; } // ---------------- // PRO INIT END // ---------------- // Is the premium features there ? if(!defined('LOGINIZER_PREMIUM')){ if(current_user_can('activate_plugins')){ // The promo time $loginizer['promo_time'] = get_option('loginizer_promo_time'); if(empty($loginizer['promo_time'])){ $loginizer['promo_time'] = time(); update_option('loginizer_promo_time', $loginizer['promo_time']); } // Are we to show the loginizer promo if(!empty($loginizer['promo_time']) && $loginizer['promo_time'] > 0 && $loginizer['promo_time'] < (time() - (30*24*3600))){ add_action('admin_notices', 'loginizer_promo'); } if(!empty($loginizer['csrf_promo']) && $loginizer['csrf_promo'] > 0 && $loginizer['csrf_promo'] < (time() - 86400)){ add_action('admin_notices', 'loginizer_csrf_promo'); } // Are we to disable the promo if(isset($_GET['loginizer_promo']) && (int)$_GET['loginizer_promo'] == 0){ update_option('loginizer_promo_time', (0 - time()) ); die('DONE'); } $loginizer['backuply_promo'] = get_option('loginizer_backuply_promo_time'); if(empty($loginizer['backuply_promo'])){ $loginizer['backuply_promo'] = abs($loginizer['promo_time']); update_option('loginizer_backuply_promo_time', $loginizer['backuply_promo']); } // Setting CSRF Promo time $loginizer['csrf_promo'] = get_option('loginizer_csrf_promo_time'); if(empty($loginizer['csrf_promo'])){ $loginizer['csrf_promo'] = abs($loginizer['promo_time']); update_option('loginizer_csrf_promo_time', $loginizer['csrf_promo']); } } } // Secuity checks for social login. if(!empty($_GET['lz_social_provider']) && loginizer_can_login()){ include_once LOGINIZER_DIR . '/main/social-login.php'; return; } } // Should return NULL if everything is fine function loginizer_wp_authenticate($user, $username, $password){ global $loginizer, $lz_error, $lz_cannot_login, $lz_user_pass; if(!empty($username) && !empty($password)){ $lz_user_pass = 1; } // Are you whitelisted ? if(loginizer_is_whitelisted()){ $loginizer['ip_is_whitelisted'] = 1; return $user; } else if (!empty($loginizer['trusted_ips'])){ $lz_cannot_login = 1; // This is used by WP Activity Log apply_filters( 'wp_login_blocked', $username ); // Shows a blocked screen if(!empty($loginizer['blocked_screen'])){ $lz_error['trusted_ip'] = __('You are restricted from logging in as your IP is not whitelisted.', 'loginizer'); loginizer_blocked_page($lz_error); } return new WP_Error('ip_blacklisted', __('You are restricted from logging in as your IP is not whitelisted.', 'loginizer')); } // Are you blacklisted ? if(loginizer_is_blacklisted()){ $lz_cannot_login = 1; // This is used by WP Activity Log apply_filters( 'wp_login_blocked', $username ); // Shows a blocked screen if(!empty($loginizer['blocked_screen'])){ loginizer_blocked_page($lz_error); } return new WP_Error('ip_blacklisted', implode('', $lz_error), 'loginizer'); } // Is the username blacklisted ? if(function_exists('loginizer_user_blacklisted')){ if(loginizer_user_blacklisted($username)){ $lz_cannot_login = 1; // This is used by WP Activity Log apply_filters( 'wp_login_blocked', $username ); return new WP_Error('user_blacklisted', implode('', $lz_error), 'loginizer'); } } if(loginizer_can_login()){ return $user; } $lz_cannot_login = 1; // This is used by WP Activity Log apply_filters( 'wp_login_blocked', $username ); // Shows a blocked screen if(!empty($loginizer['blocked_screen'])){ loginizer_blocked_page($lz_error); } return new WP_Error('ip_blocked', implode('', $lz_error), 'loginizer'); } function loginizer_can_login(){ global $wpdb, $loginizer, $lz_error; // Get the logs $sel_query = $wpdb->prepare("SELECT * FROM `".$wpdb->prefix."loginizer_logs` WHERE `ip` = %s", $loginizer['current_ip']); $result = lz_selectquery($sel_query); if(!empty($result['count']) && ($result['count'] % $loginizer['max_retries']) == 0){ // Has he reached max lockouts ? if($result['lockout'] >= $loginizer['max_lockouts']){ $loginizer['lockout_time'] = $loginizer['lockouts_extend']; } // Is he in the lockout time ? if($result['time'] >= (time() - $loginizer['lockout_time'])){ $banlift = ceil((($result['time'] + $loginizer['lockout_time']) - time()) / 60); //echo 'Current Time '.date('d/M/Y H:i:s P', time()).'
'; //echo 'Last attempt '.date('d/M/Y H:i:s P', $result['time']).'
'; //echo 'Unlock Time '.date('d/M/Y H:i:s P', $result['time'] + $loginizer['lockout_time']).'
'; $_time = $banlift.' '.$loginizer['msg']['minutes_err']; if($banlift > 60){ $banlift = ceil($banlift / 60); $_time = $banlift.' '.$loginizer['msg']['hours_err']; } $lz_error['ip_blocked'] = $loginizer['msg']['lockout_err'].' '.$_time; return false; } } return true; } function loginizer_is_blacklisted(){ global $wpdb, $loginizer, $lz_error; $blacklist = isset($loginizer['blacklist']) ? $loginizer['blacklist'] : []; if(empty($blacklist)){ return false; } foreach($blacklist as $k => $v){ // Is the IP in the blacklist ? if(inet_ptoi($v['start']) <= inet_ptoi($loginizer['current_ip']) && inet_ptoi($loginizer['current_ip']) <= inet_ptoi($v['end'])){ $result = 1; break; } // Is it in a wider range ? if(inet_ptoi($v['start']) >= 0 && inet_ptoi($v['end']) < 0){ // Since the end of the RANGE (i.e. current IP range) is beyond the +ve value of inet_ptoi, // if the current IP is <= than the start of the range, it is within the range // OR // if the current IP is <= than the end of the range, it is within the range if(inet_ptoi($v['start']) <= inet_ptoi($loginizer['current_ip']) || inet_ptoi($loginizer['current_ip']) <= inet_ptoi($v['end'])){ $result = 1; break; } } } // You are blacklisted if(!empty($result)){ $lz_error['ip_blacklisted'] = $loginizer['msg']['ip_blacklisted']; return true; } return false; } function loginizer_is_whitelisted(){ global $wpdb, $loginizer, $lz_error; $whitelist = $loginizer['whitelist']; if(empty($whitelist)){ return false; } foreach($whitelist as $k => $v){ // Is the IP in the blacklist ? if(inet_ptoi($v['start']) <= inet_ptoi($loginizer['current_ip']) && inet_ptoi($loginizer['current_ip']) <= inet_ptoi($v['end'])){ $result = 1; break; } // Is it in a wider range ? if(inet_ptoi($v['start']) >= 0 && inet_ptoi($v['end']) < 0){ // Since the end of the RANGE (i.e. current IP range) is beyond the +ve value of inet_ptoi, // if the current IP is <= than the start of the range, it is within the range // OR // if the current IP is <= than the end of the range, it is within the range if(inet_ptoi($v['start']) <= inet_ptoi($loginizer['current_ip']) || inet_ptoi($loginizer['current_ip']) <= inet_ptoi($v['end'])){ $result = 1; break; } } } // You are whitelisted if(!empty($result)){ return true; } return false; } // When the login fails, then this is called // We need to update the database function loginizer_login_failed($username, $is_2fa = ''){ global $wpdb, $loginizer, $lz_cannot_login; // Some plugins are changing the value for username as null so we need to handle it before using it for the INSERT OR UPDATE query if(empty($username) || is_null($username)){ $username = ''; } $fail_type = 'Login'; if(!empty($is_2fa)){ $fail_type = '2FA'; } if(empty($lz_cannot_login) && empty($loginizer['ip_is_whitelisted']) && empty($loginizer['no_loginizer_logs'])){ // The params which comes when social login returns an error, have some characters, which WordPress could not save. $server_uri = $_SERVER['REQUEST_URI']; if(!empty($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], 'lz_social_provider') !== FALSE){ $request_uri = explode('=', $_SERVER['REQUEST_URI']); $server_uri = $request_uri[0]; } $url = @addslashes((!empty($_SERVER['HTTPS']) ? 'https://' : 'http://').$_SERVER['HTTP_HOST'].$server_uri); $url = esc_url($url); $sel_query = $wpdb->prepare("SELECT * FROM `".$wpdb->prefix."loginizer_logs` WHERE `ip` = %s", $loginizer['current_ip']); $result = lz_selectquery($sel_query); if(!empty($result)){ $lockout = floor((($result['count']+1) / $loginizer['max_retries'])); $update_data = array('username' => $username, 'time' => time(), 'count' => $result['count']+1, 'lockout' => $lockout, 'url' => $url); $where_data = array('ip' => $loginizer['current_ip']); $format = array('%s','%d','%d','%d','%s'); $where_format = array('%s'); $wpdb->update($wpdb->prefix.'loginizer_logs', $update_data, $where_data, $format, $where_format); // Do we need to email admin ? if(!empty($loginizer['notify_email']) && $lockout >= $loginizer['notify_email']){ $lockout_time = $loginizer['lockout_time']; if($lockout >= $loginizer['max_lockouts']){ // extended lockout is in hours so we have to convert to minute $lockout_time = $loginizer['lockouts_extend']; } $sitename = lz_is_multisite() ? get_site_option('site_name') : get_option('blogname'); $mail = array(); $mail['to'] = $loginizer['notify_email_address']; $mail['subject'] = 'Failed '.$fail_type.' Attempts from IP '.$loginizer['current_ip'].' ('.$sitename.')'; $mail['message'] = 'Hi, '.($result['count']+1).' failed '.strtolower($fail_type).' attempts and '.$lockout.' lockout(s) from IP '.$loginizer['current_ip'].' on your site : '.home_url().' Last '.$fail_type.' Attempt : '.date('d/M/Y H:i:s P', time()).' Last User Attempt : '.$username.' IP has been blocked until : '.date('d/M/Y H:i:s P', time() + $lockout_time).' Regards, Loginizer'; @wp_mail($mail['to'], $mail['subject'], $mail['message']); } }else{ $result = array(); $result['count'] = 0; $insert_data = array('username' => $username, 'time' => time(), 'count' => 1, 'ip' => $loginizer['current_ip'], 'lockout' => 0, 'url' => $url); $format = array('%s','%d','%d','%s','%d','%s'); $wpdb->insert($wpdb->prefix.'loginizer_logs', $insert_data, $format); } // We need to add one as this is a failed attempt as well $result['count'] = $result['count'] + 1; loginizer_update_attempt_stats(0); $loginizer['retries_left'] = ($loginizer['max_retries'] - ($result['count'] % $loginizer['max_retries'])); $loginizer['retries_left'] = $loginizer['retries_left'] == $loginizer['max_retries'] ? 0 : $loginizer['retries_left']; } } function loginizer_login_success($user_login, $user) { global $wp_version, $loginizer; loginizer_update_attempt_stats(1); if(empty($loginizer['login_mail'])){ return; } if(empty($loginizer['login_mail']['enable'])){ return; } if(!empty($loginizer['login_mail']['disable_whitelist'])){ // Check its whitelist ip if(loginizer_is_whitelisted()){ return; } } if(empty($user_login) && empty($user)){ error_log('Loginizer: No user information to send email'); return; } if(empty($user)){ $user = get_user_by('login', $user_login); } if(empty($user)){ error_log('Loginizer: Unable to get the user'); return; } if(empty($loginizer['login_mail']['roles']) || !is_array($loginizer['login_mail']['roles'])){ return; } // Check if the user role is enabled for email notification. if(!array_intersect($user->roles, $loginizer['login_mail']['roles'])){ return; } // current_datetime & wp_timezone_string were introduced in WordPress 5.3 if(!empty($wp_version) && version_compare($wp_version, '5.3', '>') && function_exists('current_datetime')){ $time_zone = wp_timezone_string(); if(!empty($time_zone) && isset($time_zone[1]) && is_numeric($time_zone[1])){ $time_zone = 'UTC'.$time_zone; } // Setting up data variables. $date = current_datetime()->format('Y-m-d H:i:s') .' '. $time_zone; } else { $date = date("Y-m-d H:i:s", time()) . ' ' . date_default_timezone_get(); } $sitename = lz_is_multisite() ? get_site_option('site_name') : get_option('blogname'); $email = $user->data->user_email; $vars = array( 'date' => $date, 'ip' => esc_html($loginizer['current_ip']), 'sitename' => $sitename, 'user_login' => $user_login ); $message = lz_lang_vars_name($loginizer['login_mail_body'], $vars); $subject = lz_lang_vars_name($loginizer['login_mail_subject'], $vars); $headers = []; // Do we need to send the email as HTML ? if(!empty($loginizer['login_mail']['html_mail'])){ $headers[] = 'Content-Type: text/html; charset=UTF-8'; if(!empty($loginizer['login_mail']['body'])){ $message = html_entity_decode($message); }else{ $message = preg_replace("/\/i", "
", $message); $message = preg_replace('/(?)\n/i', "
\n", $message); } } // Sending notification if(empty(wp_mail($email, $subject, $message, $headers))){ error_log(__('There was a problem sending your email.', 'loginizer')); return; } } function loginizer_update_attempt_stats($type){ $stats = get_option('loginizer_login_attempt_stats', []); $time = strtotime(date('Y-m-d H:00:00')); if(empty($stats[$time][$type])){ $stats[$time][$type] = 0; } $stats[$time][$type] += 1; update_option('loginizer_login_attempt_stats', $stats, false); } // Handles the error of the password not being there function loginizer_error_handler($errors, $redirect_to){ global $wpdb, $loginizer, $lz_user_pass, $lz_cannot_login; //echo 'loginizer_error_handler :';print_r($errors->errors);echo '
'; if(is_null($errors) || empty($errors)){ return true; } // Remove the empty password error if(is_wp_error($errors)){ $codes = $errors->get_error_codes(); foreach($codes as $k => $v){ if($v == 'invalid_username' || $v == 'incorrect_password'){ $show_error = 1; } } $errors->remove('invalid_username'); $errors->remove('incorrect_password'); // Add the error if(!empty($lz_user_pass) && !empty($show_error) && empty($lz_cannot_login)){ $errors->add('invalid_userpass', 'ERROR: ' . $loginizer['msg']['inv_userpass']); } // Add the number of retires left as well if(count($errors->get_error_codes()) > 0 && isset($loginizer['retries_left'])){ $errors->add('retries_left', loginizer_retries_left()); } } return $errors; } // Handles the error of the password not being there function loginizer_woocommerce_error_handler(){ global $wpdb, $loginizer, $lz_user_pass, $lz_cannot_login; if(function_exists('wc_add_notice')){ wc_add_notice( loginizer_retries_left(), 'error' ); } } // Handles social login URL function loginizer_social_login_error_handler($errors = '', $redirect_to = ''){ global $loginizer; loginizer_get_social_error(); if(empty($loginizer['social_errors'])){ return $errors; } if(is_null($errors) || empty($errors) || !is_wp_error($errors)){ $errors = new WP_Error(); } foreach($loginizer['social_errors'] as $key => $text){ $errors->add($key, $text); } return $errors; } // Returns a string with the number of retries left function loginizer_retries_left(){ global $wpdb, $loginizer, $lz_user_pass, $lz_cannot_login; // If we are to show the number of retries left if(isset($loginizer['retries_left'])){ $retries_left = apply_filters('loginizer_retries_left_num', $loginizer['retries_left']); return ''.esc_html($retries_left).' '.$loginizer['msg']['attempts_left']; } } function loginizer_reset_retries(){ global $wpdb, $loginizer; $deltime = time() - $loginizer['reset_retries']; $del_query = $wpdb->prepare("DELETE FROM `".$wpdb->prefix."loginizer_logs` WHERE `time` <= %d", $deltime); $result = $wpdb->query($del_query); update_option('loginizer_last_reset', time()); } function loginizer_load_translation_vars(){ global $loginizer; $loginizer['login_mail_default_sub'] = __('Login Successful at $sitename', 'loginizer'); $loginizer['login_mail_default_msg'] = __('Hello $user_login, Your account was recently logged in from the IP : $ip Time : $date If it was not you who logged in then please report this to us immediately. Regards, $sitename','loginizer'); if(empty($loginizer['login_mail_subject'])){ $loginizer['login_mail_subject'] = $loginizer['login_mail_default_sub']; } if(empty($loginizer['login_mail_body'])){ $loginizer['login_mail_body'] = $loginizer['login_mail_default_msg']; } // Default messages $loginizer['d_msg']['inv_userpass'] = __('Incorrect Username or Password', 'loginizer'); $loginizer['d_msg']['ip_blacklisted'] = __('Your IP has been blacklisted', 'loginizer'); $loginizer['d_msg']['attempts_left'] = __('attempt(s) left', 'loginizer'); $loginizer['d_msg']['lockout_err'] = __('You have exceeded maximum login retries
Please try after', 'loginizer'); $loginizer['d_msg']['minutes_err'] = __('minute(s)', 'loginizer'); $loginizer['d_msg']['hours_err'] = __('hour(s)', 'loginizer'); // Message Strings $loginizer['msg'] = get_option('loginizer_msg', []); foreach($loginizer['d_msg'] as $lk => $lv){ if(empty($loginizer['msg'][$lk])){ $loginizer['msg'][$lk] = $loginizer['d_msg'][$lk]; } } $loginizer['2fa_d_msg']['otp_app'] = __('Please enter the OTP as seen in your App', 'loginizer'); $loginizer['2fa_d_msg']['otp_email'] = __('Please enter the OTP emailed to you', 'loginizer'); $loginizer['2fa_d_msg']['otp_field'] = __('One Time Password', 'loginizer'); $loginizer['2fa_d_msg']['otp_question'] = __('Please answer your security question', 'loginizer'); $loginizer['2fa_d_msg']['otp_answer'] = __('Your Answer', 'loginizer'); // Message Strings $loginizer['2fa_msg'] = get_option('loginizer_2fa_msg', []); foreach($loginizer['2fa_d_msg'] as $lk => $lv){ if(empty($loginizer['2fa_msg'][$lk])){ $loginizer['2fa_msg'][$lk] = $loginizer['2fa_d_msg'][$lk]; } } } // Checks if softaculous is installed on the server. function loginizer_check_softaculous(){ // Checking if we have Softaculous installed? if(!preg_match('/^\/home(?:\d+)?\/.*\//U', ABSPATH, $matches)){ return false; } if(empty($matches) || empty($matches[0])){ return false; } $softaculous_path = $matches[0] . '.softaculous/installations.php'; if(!file_exists($softaculous_path)){ return false; } // Checking if users has changed the branding of Softaculous. $universal_file = ''; // Plesk, ISPManager, ISPConfig, InterWorx, H-Sphere, CentOS Web Panel, Softaculous Remote and Softaculous Enterprise if(file_exists('/usr/local/softaculous/enduser/universal.php')){ $universal_file = '/usr/local/softaculous/enduser/universal.php'; }else if(file_exists('/usr/local/cpanel/whostmgr/docroot/cgi/softaculous/enduser/universal.php')){ $universal_file = '/usr/local/cpanel/whostmgr/docroot/cgi/softaculous/enduser/universal.php'; }else if(file_exists('/usr/local/directadmin/plugins/softaculous/enduser/universal.php')){ $universal_file = '/usr/local/directadmin/plugins/softaculous/enduser/universal.php'; }else if(file_exists('/usr/local/vesta/softaculous/enduser/universal.php')){ $universal_file = '/usr/local/vesta/softaculous/enduser/universal.php'; } if(empty($universal_file)){ return false; } $universal = file_get_contents($universal_file); if(empty($universal)){ return false; } // Checking if Softaculous is being whitelabeled if(preg_match('/\$globals\[["\']sn["\']\]\s.?=\s.?["\']Softaculous["\']/', $universal)){ update_option('loginizer_softwp_upgrade', time()); } return false; } // Sorry to see you going register_uninstall_hook(LOGINIZER_FILE, 'loginizer_deactivation'); function loginizer_deactivation(){ global $wpdb; $sql = array(); $sql[] = "DROP TABLE ".$wpdb->prefix."loginizer_logs;"; foreach($sql as $sk => $sv){ $wpdb->query($sv); } delete_option('loginizer_version'); delete_option('loginizer_options'); delete_option('loginizer_last_reset'); delete_option('loginizer_whitelist'); delete_option('loginizer_blacklist'); delete_option('loginizer_msg'); delete_option('loginizer_2fa_msg'); delete_option('loginizer_2fa_email_template'); delete_option('loginizer_security'); delete_option('loginizer_wp_admin'); delete_option('loginizer_csrf_promo_time'); delete_option('loginizer_backuply_promo_time'); delete_option('loginizer_promo_time'); delete_option('loginizer_ins_time'); delete_option('loginizer_2fa_whitelist'); delete_option('loginizer_checksums_last_run'); delete_option('loginizer_checksums_diff'); delete_option('loginizer_ip_method'); delete_option('loginizer_2fa_custom_redirect'); delete_option('external_updates-loginizer-security'); delete_option('loginizer_login_attempt_stats'); }license.txt000064400000063642147577535410006764 0ustar00 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! readme.txt000064400000062537147577535410006601 0ustar00=== Loginizer === Contributors: softaculous, loginizer, pagelayer Tags: security, access, admin, Loginizer, login, logs, ban ip, failed login, ip, whitelist ip, blacklist ip, failed attempts, lockouts, hack, authentication, login, security, rename login url, rename login, rename wp-admin, secure wp-admin, rename admin url, secure admin, brute force protection Requires at least: 3.0 Tested up to: 6.7 Requires PHP: 5.5 Stable tag: 1.9.7 License: LGPLv2.1 License URI: http://www.gnu.org/licenses/lgpl-2.1.html Loginizer is a WordPress security plugin which helps you fight against bruteforce attacks. == Description == Loginizer is a WordPress plugin which helps you fight against bruteforce attack by blocking login for the IP after it reaches maximum retries allowed. You can blacklist or whitelist IPs for login using Loginizer. You can use various other features like Two Factor Auth, reCAPTCHA, PasswordLess Login, etc. to improve security of your website. Loginizer is actively used by more than 1000000+ WordPress websites. You can find our official documentation at https://loginizer.com/docs. We are also active in our community support forums on wordpress.org if you are one of our free users. Our Premium Support Ticket System is at https://loginizer.deskuss.com Free Features : * Brute force protection. IPs trying to brute force your website will be blocked for 15 minutes after 3 failed login attempts. After multiple lockouts the IP is blocked for 24 hours. This is the default configuration and can be changed from Loginizer -> Brute force page in WordPress admin panel. * Failed login attempts logs. * Blacklist IPs * Whitelist IPs * Custom error messages on failed login. * Permission check for important files and folders. * Allow only Trusted IP. * Blocked Screen in place of the Login page. * Email Notification on successful login. * Let users login with LinkedIn = Get Support and Pro Features = Get professional support from our experts and pro features to take your site's security to the next level with Loginizer-Security. Pro Features : * MD5 Checksum - of Core WordPress Files. The admin can check and ignore files as well. * PasswordLess Login - At the time of Login, the username / email address will be asked and an email will be sent to the email address of that account with a temporary link to login. * Two Factor Auth via Email - On login, an email will be sent to the email address of that account with a temporary 6 digit code to complete the login. * Two Factor Auth via App - The user can configure the account with a 2FA App like Google Authenticator, Authy, etc. * Login Challenge Question - The user can setup a Challenge Question and Answer as an additional security layer. After Login, the user will need to answer the question to complete the login. * reCAPTCHA - Google's reCAPTCHA v3/v2, Cloudflare Turnstile, hCAPTCHA can be configured for the Login screen, Comments Section, Registration Form, etc. to prevent automated brute force attacks. Supports WooCommerce as well. * Rename Login Page - The Admin can rename the login URL (slug) to something different from wp-login.php to prevent automated brute force attacks. * Rename WP-Admin URL - The Admin area in WordPress is accessed via wp-admin. With loginizer you can change it to anything e.g. site-admin * CSRF Protection - This helps in preventing CSRF attacks as it updates the admin URL with a session string which makes it difficult and nearly impossible for the attacker to predict the URL. * Rename Login with Secrecy - If set, then all Login URL's will still point to wp-login.php and users will have to access the New Login Slug by typing it in the browser. * Disable XML-RPC - An option to simply disable XML-RPC in WordPress. Most of the WordPress users don't need XML-RPC and can disable it to prevent automated brute force attacks. * Rename XML-RPC - The Admin can rename the XML-RPC to something different from xmlrpc.php to prevent automated brute force attacks. * Username Auto Blacklist - Attackers generally use common usernames like admin, administrator, or variations of your domain name / business name. You can specify such username here and Loginizer will auto-blacklist the IP Address(s) of clients who try to use such username(s). * New Registration Domain Blacklist - If you would like to ban new registrations from a particular domain, you can use this utility to do so. * Change the Admin Username - The Admin can rename the admin username to something more difficult. * Auto Blacklist IPs - IPs will be auto blacklisted, if certain usernames saved by the Admin are used to login by malicious bots / users. * Disable Pingbacks - Simple way to disable PingBacks. * SSO - Single Sign-on, let any user access to your WordPress Dashboard without the need to share username or password. * Limit Concurrent Logins - It prevents user to login from different devices concurrently, you can define how many devices you want to allow, and how you want to restrict the user when concurrent limit is reached. * Social Login - Users can login or register with their Google, Github, Facebook, X (Twitter), Discord, Twitch, LinkedIn with support for WooCommerce. Features in Loginizer include: * Blocks IP after maximum retries allowed * Extended Lockout after maximum lockouts allowed * Email notification to admin after max lockouts * Blacklist IP/IP range * Whitelist IP/IP range * Check logs of failed attempts * Create IP ranges * Delete IP ranges * Licensed under LGPLv2.1 * Safe & Secure == Installation == Upload the Loginizer plugin to your blog, Activate it. That's it. You're done! == Screenshots == 1. Login Failed Error message 2. Loginizer Dashboard page 3. Loginizer Brute Force Settings page == Changelog == = 1.9.7 = * [Task] A notice has been tweaked to prevent confusion among users. = 1.9.6 = * [Task] Removed wpCentral Promo from Loginizer. = 1.9.5 = * [Task] A few typos in description of features have been fixed. = 1.9.4 = * [Task] Tested with WordPress 6.7, fixed translation Notice. * [Bug-Fix] HOTP and Base32 caused conflict with some plugins that has been fixed. = 1.9.3 = * [Security] There was a security issue in the Pro version of the plugin which has been fixed, was reported by wesley (wcraft)[Wordfence] * [Task] Improved Compatibility with Softaculous Plugin. = 1.9.2 = * [Task] Improved license handling. = 1.9.1 = * [Bug-Fix] Social Login was not working on WooCommerce or registration page, that has been fixed. * [Bug-Fix] A PHP warning has been fixed. = 1.9.0 = * [Bug-Fix] For some users there was an issue in updating that has been fixed. = 1.8.9 = * [Task] Structural changes. * [Task] Tested with WordPress 6.6. = 1.8.8 = * [Bug-Fix] Verison in one file was not updated, this has been fixed. = 1.8.7 = * [Feature] Social Login: Now you can let the users login through LinkedIn Login. * [Feature] Send Login Notification as HTML email. * [Pro Feature] Supports social login with Google, GitHub, Facebook, X(Formly Twitter) and more Login Providers. = 1.8.6 = * [Bug-Fix] There was an issue with Login Notification body and subject, it was adding \(slashes) if "(double-quotes) where being used. This has been fixed. * [Task] Removal of unwanted code. = 1.8.5 = * [Feature] Added Option to disable Login notification for whitelisted IPs. * [Improvement] We have added variables for custom subject in Login notification. * [Bug-Fix] Now the time shown in the Login Notification email, will respect the timezone set in the WordPress settings. * [Bug-Fix] Error notice when 2FA fails had some CSS issue which has been fixed. * [Task] We have remove unwanted code in reCAPTCHA. = 1.8.4 = * [Feature] Block Page, now instead of showing error on the Login page of user being blacklisted, you can just show a page with error, reducing the resource being used to show the error. * [Feature] Email notification on successful login and you can enforce this on your users too. * [Pro Feature] Added Cloudflare Turnstile, and hCaptcha. * [Task] Tested with WordPress 6.5. = 1.8.3 = * [Task] We have removed unwanted code. = 1.8.2 = * [Task] Tested on WordPress 6.4. * [Improvement] Now SSO can live for multiple Login attempts, default being 1 and maximum is 15 Login access. * [Imrpovement] Now SSO can live longer for upto 2 days. * [Bug-Fixes] A few Warning related to PHP 8.2 has been fixed = 1.8.1 = *[Bug-Fix] There was an issue while checking checksum, if the WordPress install was in en_US but the language was set to some other languages from the settings, then the checksum was comparing the checksums from the language selected in WordPress settings which is now always the language of the install, this has been fixed. = 1.8.0 = * [Feature][Pro] We have added Single Sign-on for you to create temporary login to share to let other login to your account without sharing password. * [Refactor] We have reduced the amount of code that was being loaded when a login attempt was made by around 150KB. * [Refactor] Screenshots of Loginizer were included in the plugin, we have shifted that to assets of WordPress.org, reducing the overall size of plugin by more than 100KB. = 1.7.9 = * [Bug-Fix] Users were getting PHP notice in init.php file that has been fixed. * [Bug-Fix] Math cookie has been set as secure now. * [Security] We were sanitizing an output in place of escaping it, that has been fixed [Reported by Erwan Le Rousseau from WPScan] = 1.7.8 = * [Task] Tested with WordPress 6.2 * [Feature] [Pro] Limit Concurrent user login, you can either block login attempt or revoke when limit of concurrent user is reached. * [Feature] Login attempts stats chart on Loginizer Dashboard. = 1.7.7 = * [Feature] Ability to allow only Whitelisted IP's to be able to login with Trusted IP's. * [Feature] [Pro] Option to add custom redirect on 2FA Login based on user role. * [Bug-Fix] [Pro] User's were getting redirected to WP Admin when logging in from Checkout page in Passwordless and 2FA options that has been fixed. * [Bug-Fix] Some users were getting PHP Warnings that has been fixed. = 1.7.6 = * [Security] Minor security issues reported by patchstack have been fixed with in 24 hours of reporting. * [Bug-Fix] For some themes the Maths capatch input was invisible that has been fixed. = 1.7.5 = * [Task] Tested compatibility with WordPress 6.1 * [Bug Fix] There was an issue with sanitizing URL that has been fixed. = 1.7.4 = * [Feature] CSRF Protection adds a unique session key in your admin URL when you login to it, which adds another layer of security to your WordPress website as it makes it difficult to predict the URL hence making it difficult and nearly impossible to do CSRF attacks on your WordPress admin panel. * [Task] 2FA Support for MasterStudy Custom Login * [Bug Fix] Some users were facing an error when using 2FA App verification that has been fixed. = 1.7.3 = * [Bug Fix] Added validation not to allow values less than 0 for all Brute Force admin settings. = 1.7.2 = * [Improvement] [Pro] Allowed HTML characters in Passwordless email. * [Bug Fix] Improved performance on sites running Loginizer with WooCommerce. * [Bug Fix] Added validation not to allow values less than 0 in Brute Force admin settings. * [Bug Fix] Some language strings were hardcoded in English and could not be translated. This is fixed and all strings can now be translated. * [Bug Fix] Resolved PHP Warnings and Notices on latest PHP versions. = 1.7.1 = * [Improvement] [Pro] Added error message to not allow using same slug for wp-login.php and wp-admin as it causes conflict. * [Improvement] [Pro] Added exception for readme.html, license.txt and wp-config-sample.php while checking the checksum to avoid false alarm about checksum mismatch. * [Bug Fix] [Pro] In WordPress Multisite, on changing the admin username the super admins list was not updated. This is fixed now. * [Task] Compatibility with WordPress 6.0 = 1.7.0 = * Compatible with WordPress 5.9 * [Feature] [Pro] Added option to choose recaptcha.net instead of google.com for countries that do not support google * [Bug Fix] [Pro] Fix to email the correct unblock time when an IP is blocked for extended hours. = 1.6.9 = * [Bug Fix] [Pro] Fix to not show Loginizer 2FA Security Settings in Edit Account page in WooCommerce Customer area. It will be shown in Security (registered by Loginizer) tab instead. = 1.6.8 = * [Feature] Added option to export failed login attempts to CSV file. * [Improvement] Added option to send failed login notifications to a custom email. * [Improvement] [Pro] Added support for 2FA for WooCommerce customers * [Bug Fix] [Pro] On WooCommerce customer login page the password field was not hidden when Passwordless login was enabled in Loginizer. * [Bug Fix] [Pro] Autofill enabled in the browser caused the OTP field on 2FA login to be prefilled. = 1.6.7 = * [Feature] Added Bulk Export/Import Blacklist and Whitelist IPs via CSV. * [Improvement] Added option to Blacklist selected IPs from Failed Login Attempts Logs. * [Improvement] Added external link in Brute Force logs for IP information of the IPs attempting brute force. * [Improvement] [Pro] Added Loginizer 2FA status column on Users list page to show 2FA preferences selected by users. * [Improvement] [Pro] Added Show/Hide button for OTP field on 2FA login page. * [Bug Fix] [Pro] Two Factor Authentication lead to 502 Bad Gateway error on WP Engine instances. This is resolved now. = 1.6.6 = * [Improvement] For new installs, the loginizer_logs table will now use the server default MySQL Engine. * [Improvement] For the login attempts blocked by Loginizer, some other Activity Logs plugin still reported such blocked attempt as a failed login attempt. * [Bug Fix] In rare cases when the username received in failed login attempt was blank, Loginizer failed to save such requests in the failed login logs table. This is fixed now. = 1.6.5 = * [Bug Fix] After Interim Login due to session timeout, the popup for login was not closed. This is fixed now. * [Bug Fix] reCAPTCHA was not working on registration page with BuddyPress plugin. This is fixed now. = 1.6.4 = This version includes a security fix and we recommend all users to upgrade to 1.6.4 or higher immediately. * [Security Fix] : A properly crafted username used to login could lead to SQL injection. This has been fixed by using the prepare function in PHP which prepares the SQL query for safe execution. * [Security Fix] : If the IP HTTP header was modified to have a null byte it could lead to stored XSS. This has been fixed by properly sanitizing the IP HTTP header before using the same. = 1.6.3 = * [Fix] Fixed a PHP Notice that was caused by a change released yesterday. = 1.6.2 = * [Feature] Added option to send Password Less Login email as HTML. * [Fix] When reCAPTCHA was disabled on Woocommerce checkout page, Loginizer reported captcha error if a user tried to register on checkout page. This is fixed now. * [Fix] The email sent to admin for brute force login attempts will now contain the site url as well. * [Fix] Fixed PHP Notice on Two Factor Authentication page. = 1.6.1 = * [Fix] The captcha on Registration form when using WooCommerce was not being rendered if the "WooCommerce Checkout" captcha setting was disabled in Loginizer. This is fixed now and this captcha can be disabled with "Registration Form" captcha setting in Loginizer. * [Fix] Minor checkbox pre-filling UI fix on Two Factor Authentication page. = 1.6.0 = * [Feature] Admin can white list an IP or an IP range for Two Factor Authentication. * [Fix] If the plugins or themes which are included in the default WordPress package were not updated, the Checksum reported that the files for such plugins and themes did not matched. This is fixed now. = 1.5.9 = * [Task] Admins can now customize email template for 2FA OTP via email. * [Task] Admins can now customize the 2FA messages on login screen. * [Fix] Changed the OTP via App field on login page to password type. = 1.5.8 = * [Task] Permission for / folder was suggested as 0755 and 0750 permission which is secure was reported as insecure. This is fixed now. * [Fix] Prevent PHP Deprecated Warning on plugin upgrade page on servers running PHP 7.3+ = 1.5.7 = * [Fix] Prevent PHP Notice on 1st failed login attempt from an IP. = 1.5.6 = * [Task] Admins can now subscribe to our newsletter if they decide to opt-in. = 1.5.5 = * [Bug Fix] Remember me during login was not working with 2FA features. This is fixed. * [Task] Loginizer is now supported for translation via WordPress. * [Task] Added option to fully customize the Lockout error message. = 1.5.4 = * [Task] Added option to customize Lockout Error message. = 1.5.3 = * [Task] Compatible with WordPress 5.5 * [Bug Fix] Due to a conflict with some plugin the upgrade for Loginizer Premium version did not work. This is fixed. = 1.5.2 = * [Task] Some strings were not available in translations. They can now be translated. = 1.5.1 = * [Task] Allowed to change the username of any administrator account. Previously it was supported only for user id 1 * [Bug Fix] Fixed some lines that generated PHP notice = 1.5.0 = * [Task] Admins can now customize "attempt(s) left" error message. = 1.4.9 = * [Bug Fix] Prevent brute force on 2FA pages. = 1.4.8 = * [Premium Feature] Added Google reCAPTCHA v3 and v2 invisible. = 1.4.7 = * [Security Fix] Our team internally conducted a security audit and have fixed couple of security issues. We recommend all users to upgrade to the latest version asap. = 1.4.6 = * [Task] Added Timezone offset in the Brute Force attempts list to get the exact time of the failed login attempt. * [Bug Fix] For HTTP_X_FORWARDED_FOR if the value had multiple IPs including proxied IPs, the user IP detection failed. This is fixed. * [Bug Fix] Undefined variable was used in the title on the dashboard page. This is fixed. = 1.4.5 = * [Announcement] Loginizer has joined forces with Softaculous team. * [Task] Added OTP validity time in the email sent for OTP for login. * [Bug Fix] In the premium version the Math Captcha used to fail in some conditions. This is fixed. = 1.4.4 = * [Task] Made Loginizer compatible with PHP 7.4 * [Bug Fix] The password field was not hidden in some themes for PasswordLess Login. This is fixed. = 1.4.3 = * [Bug Fix] At the time of login if recaptcha or 2FA using OTP was enabled and if you check mark on "Remember me", the login used to go to an invalid redirect URL and did not load anything. This has been fixed now. = 1.4.2 = * [Task] Tested up to: WordPress 5.2.0 * [Bug Fix] Placement of Captcha corrected for WooCommerce at the time of checkout for end users. * [Bug Fix] Checksum check shall now skip for the files which are present in default WordPress package and does not exist in the installation like deleted theme(s)/plugin(s). * [Bug Fix] Grammar correction = 1.4.1 = * [Task] Tested up to: WordPress 5.0.2 * [Task] Refresh license will throw an error if the response received from our server is invalid * [Bug Fix] The OTP input box (with respect to 2FA via SMS) was empty if the user was freshly registered and did not login at all. This is fixed. = 1.4.0 = * [Feature] New Registration Domain Blacklist - If you would like to ban new registrations from a particular domain, you can use this utility to do so. * [Feature] Made Loginizer Security for BuddyPress compatibility. * [Task] Added a method to reset wp-admin rename settings if you get locked out. * [Bug Fix] There is an XSS bug introduced in version 1.3.8. This is fixed. Please upgrade ASAP. * [Bug Fix] In the user 2FA security wizard, the default selected option was wrongly shown when the user had not set any preference for 2FA. This is fixed. = 1.3.9 = * [Feature] Added an option to Enable / Disable Brute Force checks. * [Feature] Added the feature to log the URL of the page from which the brute force attempt is being made. * [Bug Fix] Blanking the login slug used to show the value after submission. This is fixed. * [Bug Fix] Allowed HTML chars in wp_admin_msg for renaming WP-ADMIN. = 1.3.8 = * [Feature] Added Roles selection for Two Factor Authentication. The Admin can now enable 2FA for specific roles. * [Feature] Added a Tester for WP-Admin Slug Renaming feature. Now you can test the new slug before saving it. * [Feature] Added option to customize the Passwordless email being sent to the user. * [Feature] Added a custom WP-Admin restriction message if wp-admin is restricted. * [Feature] Added an option to Delete the entire Blacklist / Whitelist IP Ranges. * [Feature] Custom IP Header added as an option for detecting the IP as per the Proxy settings of a server. * [Task] Added an option to clear reCAPTCHA settings. * [Task] Added Debugger in Updater * [Task] Updater will show "Install License Key to check for Updates" * [Bug Fix] In WooCommerce the number of login retries left was not being shown. This is fixed. = 1.3.7 = * [Bug Fix] Blacklist and Whitelist IPs were not being deleted. This is fixed. = 1.3.6 = * [Feature] Pagination added to the Blacklist and Whitelist IPs * [Bug Fix] There used to be a login issue over SSL when wp-admin area is renamed. This is fixed. * [Bug Fix] SQL Injection fix for X-Forwarded-For. This is fixed. Vulnerability was found by Jonas Lejon of WPScans.com * [Bug Fix] There was a missing referrer check in Blacklist and Whitelist IP Wizard. This is fixed. = 1.3.5 = * [Feature] Added a simple Math Captcha to show Maths Questions, if someone doesn’t want to use Google Captcha * [Feature] Added a wizard for admins to set their own language strings for Brute Force messages * [Bug Fix] In WooCommerce the Lost Password, Reset Password and Comment Form captcha verification failed. This is fixed. * [Bug Fix] Hide Captcha for logged in users was not working. This is fixed. * [Bug Fix] Twitter box shown in Loginizer was not accessed over HTTPS. = 1.3.4 = * [Bug Fix] Fixed the BigInteger Class for PHP 7 compatibility. = 1.3.3 = * [Feature] IPv6 support has been added. * [Feature] The last attempted username will now be shown in the Login Logs. * [Bug Fix] If the login page had been renamed, and wp-login.php was accessed over HTTPS, the login screen was shown instead of 404 not found. This is now fixed. * [Bug Fix] If the user had used a "/" in the rename login slug, the new slug would not work without the "/" in the URL. This is now fixed. * [Bug Fix] The license key could get reset in some cases. This also caused plugin updates to fail. This is now fixed. * [Bug Fix] Wild Cards “*” in the Username Auto Blacklist did not work. This is now fixed. * [Bug Fix] The documentation in the plugin was pointing to a wrong link. This is now fixed. = 1.3.2 = * [Feature] Rename the wp-admin access URL is now possible with Loginizer * [Feature] WooCommerce support has been improved for reCAPTCHA * [Feature] Loginizer will now show a Notification to the Enduser to setup the preferred 2FA settings * [Feature] Added option to choose between REMOTE_ADDR, HTTP_CLIENT_IP and HTTP_X_FORWARDED for websites behind a proxy * [Task] Multiple reCAPTHCA on a single page is now supported * [Task] Added a link to Google's reCAPTCHA website for easy access in our reCAPTCHA wizard = 1.3.1 = * [Feature] Admin's can now remove a user's Two Factor Authentication if needed * [Feature] Added an option to change the Admin Username * [Feature] Auto Blacklist IPs if certain usernames saved by the Admin are used to login by malicious bots / users * [Feature] The Login attempt logs will now be shown as per the last attempt TIME and in Descending Order * [Feature] Added an option to Reset the Login attempts for all or specific IPs = 1.3.0 = * [Feature] Added MD5 File Checksum feature. If any core files are changed, Loginizer will log the differences and notify the Admin * [Feature] Added an option to make Email OTP as the default Two Factor Auth when a user has not set the OTP method of their choice * [Feature] Added WooCommerce support for Captcha forms * [Feature] Added pagination in the Brute Force Logs Wizard * [Bug Fix] Disabling and Re-Enabling Loginizer caused an SQL error = 1.2.0 = * [Feature] Rename Login with Secrecy : If set, then all Login URL's will still point to wp-login.php and users will have to access the New Login Slug by typing it in the browser. * [Task] The brute force logs will now be sorted as per the time of failed login attempts * [Bug Fix] Dashboard showed wrong permissions if wp-content path had been changed * [Bug Fix] Added Directory path to include files which caused issues with some plugins = 1.1.1 = * [Bug Fix] Added ABSPATH instead of get_home_path() = 1.1.0 = * [Feature] PasswordLess Login * [Feature] Two Factor Auth - Email * [Feature] Two Factor Auth - App * [Feature] Login Challenge Question * [Feature] reCAPTCHA * [Feature] Rename Login Page * [Feature] Disable XML-RPC * [Feature] Rename XML-RPC * [Feature] Disable Pingbacks * [Feature] New Dashboard * [Feature] System Information added in the new Dashboard * [Feature] File Permissions added in the new Dashboard * [Feature] New UI * [Bug Fix] Fixed bug to add IP Range from 0.0.0.1 - 255.255.255.255 * [Bug Fix] Removed /e from preg_replace causing warnings in PHP = 1.0.2 = * Fixed Extended Lockout bug * Fixed Lockout bug * Handle login attempts via XML-RPC = 1.0.1 = * Database structure changes to make the plugin work faster * Minor fixes = 1.0 = * Blocks IP after maximum retries allowed * Extended Lockout after maximum lockouts allowed * Email notification to admin after max lockouts * Blacklist IP/IP range * Whitelist IP/IP range * Check logs of failed attempts * Create IP ranges * Delete IP ranges * Licensed under LGPLv2.1 * Safe & Secure