s?-1:1),g=ao.sum(c),v=g?(s-l*p)/g:0,d=ao.range(l),y=[];return null!=e&&d.sort(e===bl?function(n,t){return c[t]-c[n]}:function(n,t){return e(o[n],o[t])}),d.forEach(function(n){y[n]={data:o[n],value:a=c[n],startAngle:f,endAngle:f+=a*v+p,padAngle:h}}),y}var t=Number,e=bl,r=0,i=Ho,u=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(i=t,n):i},n.padAngle=function(t){return arguments.length?(u=t,n):u},n};var bl={};ao.layout.stack=function(){function n(a,l){if(!(h=a.length))return a;var c=a.map(function(e,r){return t.call(n,e,r)}),f=c.map(function(t){return t.map(function(t,e){return[u.call(n,t,e),o.call(n,t,e)]})}),s=e.call(n,f,l);c=ao.permute(c,s),f=ao.permute(f,s);var h,p,g,v,d=r.call(n,f,l),y=c[0].length;for(g=0;y>g;++g)for(i.call(n,c[0][g],v=d[g],f[0][g][1]),p=1;h>p;++p)i.call(n,c[p][g],v+=f[p-1][g][1],f[p][g][1]);return a}var t=m,e=gi,r=vi,i=pi,u=si,o=hi;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:_l.get(t)||gi,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:wl.get(t)||vi,n):r},n.x=function(t){return arguments.length?(u=t,n):u},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(i=t,n):i},n};var _l=ao.map({"inside-out":function(n){var t,e,r=n.length,i=n.map(di),u=n.map(yi),o=ao.range(r).sort(function(n,t){return i[n]-i[t]}),a=0,l=0,c=[],f=[];for(t=0;r>t;++t)e=o[t],l>a?(a+=u[e],c.push(e)):(l+=u[e],f.push(e));return f.reverse().concat(c)},reverse:function(n){return ao.range(n.length).reverse()},"default":gi}),wl=ao.map({silhouette:function(n){var t,e,r,i=n.length,u=n[0].length,o=[],a=0,l=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;u>e;++e)l[e]=(a-o[e])/2;return l},wiggle:function(n){var t,e,r,i,u,o,a,l,c,f=n.length,s=n[0],h=s.length,p=[];for(p[0]=l=c=0,e=1;h>e;++e){for(t=0,i=0;f>t;++t)i+=n[t][e][1];for(t=0,u=0,a=s[e][0]-s[e-1][0];f>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;u+=o*n[t][e][1]}p[e]=l-=i?u/i*a:0,c>l&&(c=l)}for(e=0;h>e;++e)p[e]-=c;return p},expand:function(n){var t,e,r,i=n.length,u=n[0].length,o=1/i,a=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];if(r)for(t=0;i>t;t++)n[t][e][1]/=r;else for(t=0;i>t;t++)n[t][e][1]=o}for(e=0;u>e;++e)a[e]=0;return a},zero:vi});ao.layout.histogram=function(){function n(n,u){for(var o,a,l=[],c=n.map(e,this),f=r.call(this,c,u),s=i.call(this,f,c,u),u=-1,h=c.length,p=s.length-1,g=t?1:1/h;++u0)for(u=-1;++u=f[0]&&a<=f[1]&&(o=l[ao.bisect(s,a,1,p)-1],o.y+=g,o.push(n[u]));return l}var t=!0,e=Number,r=bi,i=Mi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(i="number"==typeof t?function(n){return xi(n,t)}:En(t),n):i},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ao.layout.pack=function(){function n(n,u){var o=e.call(this,n,u),a=o[0],l=i[0],c=i[1],f=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,oi(a,function(n){n.r=+f(n.value)}),oi(a,Ni),r){var s=r*(t?1:Math.max(2*a.r/l,2*a.r/c))/2;oi(a,function(n){n.r+=s}),oi(a,Ni),oi(a,function(n){n.r-=s})}return Ci(a,l/2,c/2,t?1:1/Math.max(2*a.r/l,2*a.r/c)),o}var t,e=ao.layout.hierarchy().sort(_i),r=0,i=[1,1];return n.size=function(t){return arguments.length?(i=t,n):i},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},ii(n,e)},ao.layout.tree=function(){function n(n,i){var f=o.call(this,n,i),s=f[0],h=t(s);if(oi(h,e),h.parent.m=-h.z,ui(h,r),c)ui(s,u);else{var p=s,g=s,v=s;ui(s,function(n){n.xg.x&&(g=n),n.depth>v.depth&&(v=n)});var d=a(p,g)/2-p.x,y=l[0]/(g.x+a(g,p)/2+d),m=l[1]/(v.depth||1);ui(s,function(n){n.x=(n.x+d)*y,n.y=n.depth*m})}return f}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var i,u=t.children,o=0,a=u.length;a>o;++o)r.push((u[o]=i={_:u[o],parent:t,children:(i=u[o].children)&&i.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=i);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Di(n);var u=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-u):n.z=u}else r&&(n.z=r.z+a(n._,r._));n.parent.A=i(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function i(n,t,e){if(t){for(var r,i=n,u=n,o=t,l=i.parent.children[0],c=i.m,f=u.m,s=o.m,h=l.m;o=Ti(o),i=qi(i),o&&i;)l=qi(l),u=Ti(u),u.a=n,r=o.z+s-i.z-c+a(o._,i._),r>0&&(Ri(Pi(o,n,e),n,r),c+=r,f+=r),s+=o.m,c+=i.m,h+=l.m,f+=u.m;o&&!Ti(u)&&(u.t=o,u.m+=s-f),i&&!qi(l)&&(l.t=i,l.m+=c-h,e=n)}return e}function u(n){n.x*=l[0],n.y=n.depth*l[1]}var o=ao.layout.hierarchy().sort(null).value(null),a=Li,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(c=null==(l=t)?u:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:u,n):c?l:null},ii(n,o)},ao.layout.cluster=function(){function n(n,u){var o,a=t.call(this,n,u),l=a[0],c=0;oi(l,function(n){var t=n.children;t&&t.length?(n.x=ji(t),n.y=Ui(t)):(n.x=o?c+=e(n,o):0,n.y=0,o=n)});var f=Fi(l),s=Hi(l),h=f.x-e(f,s)/2,p=s.x+e(s,f)/2;return oi(l,i?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(p-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),a}var t=ao.layout.hierarchy().sort(null).value(null),e=Li,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ao.layout.treemap=function(){function n(n,t){for(var e,r,i=-1,u=n.length;++it?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var u=e.children;if(u&&u.length){var o,a,l,c=s(e),f=[],h=u.slice(),g=1/0,v="slice"===p?c.dx:"dice"===p?c.dy:"slice-dice"===p?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),f.area=0;(l=h.length)>0;)f.push(o=h[l-1]),f.area+=o.area,"squarify"!==p||(a=r(f,v))<=g?(h.pop(),g=a):(f.area-=f.pop().area,i(f,v,c,!1),v=Math.min(c.dx,c.dy),f.length=f.area=0,g=1/0);f.length&&(i(f,v,c,!0),f.length=f.area=0),u.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var u,o=s(t),a=r.slice(),l=[];for(n(a,o.dx*o.dy/t.value),l.area=0;u=a.pop();)l.push(u),l.area+=u.area,null!=u.z&&(i(l,u.z?o.dx:o.dy,o,!a.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,i=0,u=1/0,o=-1,a=n.length;++oe&&(u=e),e>i&&(i=e));return r*=r,t*=t,r?Math.max(t*i*g/r,r/(t*u*g)):1/0}function i(n,t,e,r){var i,u=-1,o=n.length,a=e.x,c=e.y,f=t?l(n.area/t):0;
+if(t==e.dx){for((r||f>e.dy)&&(f=e.dy);++ue.dx)&&(f=e.dx);++ue&&(t=1),1>e&&(n=0),function(){var e,r,i;do e=2*Math.random()-1,r=2*Math.random()-1,i=e*e+r*r;while(!i||i>1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var n=ao.random.normal.apply(ao,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ao.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ao.scale={};var Sl={floor:m,ceil:m};ao.scale.linear=function(){return Wi([0,1],[0,1],Mr,!1)};var kl={s:1,g:1,p:1,r:1,e:1};ao.scale.log=function(){return ru(ao.scale.linear().domain([0,1]),10,!0,[1,10])};var Nl=ao.format(".0e"),El={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ao.scale.pow=function(){return iu(ao.scale.linear(),1,[0,1])},ao.scale.sqrt=function(){return ao.scale.pow().exponent(.5)},ao.scale.ordinal=function(){return ou([],{t:"range",a:[[]]})},ao.scale.category10=function(){return ao.scale.ordinal().range(Al)},ao.scale.category20=function(){return ao.scale.ordinal().range(Cl)},ao.scale.category20b=function(){return ao.scale.ordinal().range(zl)},ao.scale.category20c=function(){return ao.scale.ordinal().range(Ll)};var Al=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Cl=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),zl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),Ll=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);ao.scale.quantile=function(){return au([],[])},ao.scale.quantize=function(){return lu(0,1,[0,1])},ao.scale.threshold=function(){return cu([.5],[0,1])},ao.scale.identity=function(){return fu([0,1])},ao.svg={},ao.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),f=o.apply(this,arguments)-Io,s=a.apply(this,arguments)-Io,h=Math.abs(s-f),p=f>s?0:1;if(n>c&&(g=c,c=n,n=g),h>=Oo)return t(c,p)+(n?t(n,1-p):"")+"Z";var g,v,d,y,m,M,x,b,_,w,S,k,N=0,E=0,A=[];if((y=(+l.apply(this,arguments)||0)/2)&&(d=u===ql?Math.sqrt(n*n+c*c):+u.apply(this,arguments),p||(E*=-1),c&&(E=tn(d/c*Math.sin(y))),n&&(N=tn(d/n*Math.sin(y)))),c){m=c*Math.cos(f+E),M=c*Math.sin(f+E),x=c*Math.cos(s-E),b=c*Math.sin(s-E);var C=Math.abs(s-f-2*E)<=Fo?0:1;if(E&&yu(m,M,x,b)===p^C){var z=(f+s)/2;m=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else m=M=0;if(n){_=n*Math.cos(s-N),w=n*Math.sin(s-N),S=n*Math.cos(f+N),k=n*Math.sin(f+N);var L=Math.abs(f-s+2*N)<=Fo?0:1;if(N&&yu(_,w,S,k)===1-p^L){var q=(f+s)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Uo&&(g=Math.min(Math.abs(c-n)/2,+i.apply(this,arguments)))>.001){v=c>n^p?0:1;var T=g,R=g;if(Fo>h){var D=null==S?[_,w]:null==x?[m,M]:Re([m,M],[S,k],[x,b],[_,w]),P=m-D[0],U=M-D[1],j=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*j+U*F)/(Math.sqrt(P*P+U*U)*Math.sqrt(j*j+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(g,(n-O)/(H-1)),T=Math.min(g,(c-O)/(H+1))}if(null!=x){var I=mu(null==S?[_,w]:[S,k],[m,M],c,T,p),Y=mu([x,b],[_,w],c,T,p);g===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-p^yu(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",p," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",m,",",M);if(null!=S){var Z=mu([m,M],[S,k],n,-R,p),V=mu([_,w],null==x?[m,M]:[x,b],n,-R,p);g===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",p^yu(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-p," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",m,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",p," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-p," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hu,r=pu,i=su,u=ql,o=gu,a=vu,l=du;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(i=En(t),n):i},n.padRadius=function(t){return arguments.length?(u=t==ql?ql:En(t),n):u},n.startAngle=function(t){return arguments.length?(o=En(t),n):o},n.endAngle=function(t){return arguments.length?(a=En(t),n):a},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Io;return[Math.cos(t)*n,Math.sin(t)*n]},n};var ql="auto";ao.svg.line=function(){return Mu(m)};var Tl=ao.map({linear:xu,"linear-closed":bu,step:_u,"step-before":wu,"step-after":Su,basis:zu,"basis-open":Lu,"basis-closed":qu,bundle:Tu,cardinal:Eu,"cardinal-open":ku,"cardinal-closed":Nu,monotone:Fu});Tl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Rl=[0,2/3,1/3,0],Dl=[0,1/3,2/3,0],Pl=[0,1/6,2/3,1/6];ao.svg.line.radial=function(){var n=Mu(Hu);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wu.reverse=Su,Su.reverse=wu,ao.svg.area=function(){return Ou(m)},ao.svg.area.radial=function(){var n=Ou(Hu);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ao.svg.chord=function(){function n(n,a){var l=t(this,u,n,a),c=t(this,o,n,a);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?i(l.r,l.p1,l.r,l.p0):i(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+i(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var i=t.call(n,e,r),u=a.call(n,i,r),o=l.call(n,i,r)-Io,f=c.call(n,i,r)-Io;return{r:u,a0:o,a1:f,p0:[u*Math.cos(o),u*Math.sin(o)],p1:[u*Math.cos(f),u*Math.sin(f)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Fo)+",1 "+t}function i(n,t,e,r){return"Q 0,0 "+r}var u=Me,o=xe,a=Iu,l=gu,c=vu;return n.radius=function(t){return arguments.length?(a=En(t),n):a},n.source=function(t){return arguments.length?(u=En(t),n):u},n.target=function(t){return arguments.length?(o=En(t),n):o},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},ao.svg.diagonal=function(){function n(n,i){var u=t.call(this,n,i),o=e.call(this,n,i),a=(u.y+o.y)/2,l=[u,{x:u.x,y:a},{x:o.x,y:a},o];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yu;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ao.svg.diagonal.radial=function(){var n=ao.svg.diagonal(),t=Yu,e=n.projection;return n.projection=function(n){return arguments.length?e(Zu(t=n)):t},n},ao.svg.symbol=function(){function n(n,r){return(Ul.get(t.call(this,n,r))||$u)(e.call(this,n,r))}var t=Xu,e=Vu;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Ul=ao.map({circle:$u,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Fl)),e=t*Fl;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ao.svg.symbolTypes=Ul.keys();var jl=Math.sqrt(3),Fl=Math.tan(30*Yo);Co.transition=function(n){for(var t,e,r=Hl||++Zl,i=Ku(n),u=[],o=Ol||{time:Date.now(),ease:Nr,delay:0,duration:250},a=-1,l=this.length;++au;u++){i.push(t=[]);for(var e=this[u],a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return Wu(i,this.namespace,this.id)},Yl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(i){i[r][e].tween.set(n,t)})},Yl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function i(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function u(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?$r:Mr,a=ao.ns.qualify(n);return Ju(this,"attr."+n,t,a.local?u:i)},Yl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(i));return r&&function(n){this.setAttribute(i,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(i.space,i.local));return r&&function(n){this.setAttributeNS(i.space,i.local,r(n))}}var i=ao.ns.qualify(n);return this.tween("attr."+n,i.local?r:e)},Yl.style=function(n,e,r){function i(){this.style.removeProperty(n)}function u(e){return null==e?i:(e+="",function(){var i,u=t(this).getComputedStyle(this,null).getPropertyValue(n);return u!==e&&(i=Mr(u,e),function(t){this.style.setProperty(n,i(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ju(this,"style."+n,e,u)},Yl.styleTween=function(n,e,r){function i(i,u){var o=e.call(this,i,u,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,i)},Yl.text=function(n){return Ju(this,"text",n,Gu)},Yl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Yl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ao.ease.apply(ao,arguments)),Y(this,function(r){r[e][t].ease=n}))},Yl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,i,u){r[e][t].delay=+n.call(r,r.__data__,i,u)}:(n=+n,function(r){r[e][t].delay=n}))},Yl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,i,u){r[e][t].duration=Math.max(1,n.call(r,r.__data__,i,u))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Yl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var i=Ol,u=Hl;try{Hl=e,Y(this,function(t,i,u){Ol=t[r][e],n.call(t,t.__data__,i,u)})}finally{Ol=i,Hl=u}}else Y(this,function(i){var u=i[r][e];(u.event||(u.event=ao.dispatch("start","end","interrupt"))).on(n,t)});return this},Yl.transition=function(){for(var n,t,e,r,i=this.id,u=++Zl,o=this.namespace,a=[],l=0,c=this.length;c>l;l++){a.push(n=[]);for(var t=this[l],f=0,s=t.length;s>f;f++)(e=t[f])&&(r=e[o][i],Qu(e,f,o,u,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wu(a,o,u)},ao.svg.axis=function(){function n(n){n.each(function(){var n,c=ao.select(this),f=this.__chart__||e,s=this.__chart__=e.copy(),h=null==l?s.ticks?s.ticks.apply(s,a):s.domain():l,p=null==t?s.tickFormat?s.tickFormat.apply(s,a):m:t,g=c.selectAll(".tick").data(h,s),v=g.enter().insert("g",".domain").attr("class","tick").style("opacity",Uo),d=ao.transition(g.exit()).style("opacity",Uo).remove(),y=ao.transition(g.order()).style("opacity",1),M=Math.max(i,0)+o,x=Zi(s),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),ao.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=y.select("line"),C=g.select("text").text(p),z=v.select("text"),L=y.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=no,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*u+"V0H"+x[1]+"V"+q*u)):(n=to,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*u+","+x[0]+"H0V"+x[1]+"H"+q*u)),E.attr(N,q*i),z.attr(k,q*M),A.attr(S,0).attr(N,q*i),L.attr(w,0).attr(k,q*M),s.rangeBand){var T=s,R=T.rangeBand()/2;f=s=function(n){return T(n)+R}}else f.rangeBand?f=s:d.call(n,s,f);v.call(n,f,s),y.call(n,s,s)})}var t,e=ao.scale.linear(),r=Vl,i=6,u=6,o=3,a=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Xl?t+"":Vl,n):r},n.ticks=function(){return arguments.length?(a=co(arguments),n):a},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(i=+t,u=+arguments[e-1],n):i},n.innerTickSize=function(t){return arguments.length?(i=+t,n):i},n.outerTickSize=function(t){return arguments.length?(u=+t,n):u},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Vl="bottom",Xl={top:1,right:1,bottom:1,left:1};ao.svg.brush=function(){function n(t){t.each(function(){var t=ao.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,m);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return $l[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,s=ao.transition(t),h=ao.transition(o);c&&(l=Zi(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(s)),f&&(l=Zi(f),h.attr("y",l[0]).attr("height",l[1]-l[0]),i(s)),e(s)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",s[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1]-s[0])}function i(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function u(){function u(){32==ao.event.keyCode&&(C||(M=null,L[0]-=s[1],L[1]-=h[1],C=2),S())}function v(){32==ao.event.keyCode&&2==C&&(L[0]+=s[1],L[1]+=h[1],C=0,S())}function d(){var n=ao.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(ao.event.altKey?(M||(M=[(s[0]+s[1])/2,(h[0]+h[1])/2]),L[0]=s[+(n[0]f?(i=r,r=f):i=f),v[0]!=r||v[1]!=i?(e?a=null:o=null,v[0]=r,v[1]=i,!0):void 0}function m(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ao.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=ao.select(ao.event.target),w=l.of(b,arguments),k=ao.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&f,C=_.classed("extent"),z=W(b),L=ao.mouse(b),q=ao.select(t(b)).on("keydown.brush",u).on("keyup.brush",v);if(ao.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",m):q.on("mousemove.brush",d).on("mouseup.brush",m),k.interrupt().selectAll("*").interrupt(),C)L[0]=s[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[s[1-T]-L[0],h[1-R]-L[1]],L[0]=s[T],L[1]=h[R]}else ao.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),ao.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,l=N(n,"brushstart","brush","brushend"),c=null,f=null,s=[0,0],h=[0,0],p=!0,g=!0,v=Bl[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:s,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Hl?ao.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,s=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(s,t.x),r=xr(h,t.y);return o=a=null,function(i){s=t.x=e(i),h=t.y=r(i),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=Bl[!c<<1|!f],n):c},n.y=function(t){return arguments.length?(f=t,v=Bl[!c<<1|!f],n):f},n.clamp=function(t){return arguments.length?(c&&f?(p=!!t[0],g=!!t[1]):c?p=!!t:f&&(g=!!t),n):c&&f?[p,g]:c?p:f?g:null},n.extent=function(t){var e,r,i,u,l;return arguments.length?(c&&(e=t[0],r=t[1],f&&(e=e[0],r=r[0]),o=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),e==s[0]&&r==s[1]||(s=[e,r])),f&&(i=t[0],u=t[1],c&&(i=i[1],u=u[1]),a=[i,u],f.invert&&(i=f(i),u=f(u)),i>u&&(l=i,i=u,u=l),i==h[0]&&u==h[1]||(h=[i,u])),n):(c&&(o?(e=o[0],r=o[1]):(e=s[0],r=s[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),f&&(a?(i=a[0],u=a[1]):(i=h[0],u=h[1],f.invert&&(i=f.invert(i),u=f.invert(u)),i>u&&(l=i,i=u,u=l))),c&&f?[[e,i],[r,u]]:c?[e,r]:f&&[i,u])},n.clear=function(){return n.empty()||(s=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!c&&s[0]==s[1]||!!f&&h[0]==h[1]},ao.rebind(n,l,"on")};var $l={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Bl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Wl=ga.format=xa.timeFormat,Jl=Wl.utc,Gl=Jl("%Y-%m-%dT%H:%M:%S.%LZ");Wl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?eo:Gl,eo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},eo.toString=Gl.toString,ga.second=On(function(n){return new va(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ga.seconds=ga.second.range,ga.seconds.utc=ga.second.utc.range,ga.minute=On(function(n){return new va(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ga.minutes=ga.minute.range,ga.minutes.utc=ga.minute.utc.range,ga.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new va(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ga.hours=ga.hour.range,ga.hours.utc=ga.hour.utc.range,ga.month=On(function(n){return n=ga.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ga.months=ga.month.range,ga.months.utc=ga.month.utc.range;var Kl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ql=[[ga.second,1],[ga.second,5],[ga.second,15],[ga.second,30],[ga.minute,1],[ga.minute,5],[ga.minute,15],[ga.minute,30],[ga.hour,1],[ga.hour,3],[ga.hour,6],[ga.hour,12],[ga.day,1],[ga.day,2],[ga.week,1],[ga.month,1],[ga.month,3],[ga.year,1]],nc=Wl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),tc={range:function(n,t,e){return ao.range(Math.ceil(n/e)*e,+t,e).map(io)},floor:m,ceil:m};Ql.year=ga.year,ga.scale=function(){return ro(ao.scale.linear(),Ql,nc)};var ec=Ql.map(function(n){return[n[0].utc,n[1]]}),rc=Jl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);ec.year=ga.year.utc,ga.scale.utc=function(){return ro(ao.scale.linear(),ec,rc)},ao.text=An(function(n){return n.responseText}),ao.json=function(n,t){return Cn(n,"application/json",uo,t)},ao.html=function(n,t){return Cn(n,"text/html",oo,t)},ao.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=ao,define(ao)):"object"==typeof module&&module.exports?module.exports=ao:this.d3=ao}();
\ No newline at end of file
diff --git a/coverage/_js/file.js b/coverage/_js/file.js
new file mode 100644
index 00000000..29cacd4d
--- /dev/null
+++ b/coverage/_js/file.js
@@ -0,0 +1,62 @@
+ $(function() {
+ var $window = $(window)
+ , $top_link = $('#toplink')
+ , $body = $('body, html')
+ , offset = $('#code').offset().top
+ , hidePopover = function ($target) {
+ $target.data('popover-hover', false);
+
+ setTimeout(function () {
+ if (!$target.data('popover-hover')) {
+ $target.popover('hide');
+ }
+ }, 300);
+ };
+
+ $top_link.hide().click(function(event) {
+ event.preventDefault();
+ $body.animate({scrollTop:0}, 800);
+ });
+
+ $window.scroll(function() {
+ if($window.scrollTop() > offset) {
+ $top_link.fadeIn();
+ } else {
+ $top_link.fadeOut();
+ }
+ }).scroll();
+
+ $('.popin')
+ .popover({trigger: 'manual'})
+ .on({
+ 'mouseenter.popover': function () {
+ var $target = $(this);
+ var $container = $target.children().first();
+
+ $target.data('popover-hover', true);
+
+ // popover already displayed
+ if ($target.next('.popover').length) {
+ return;
+ }
+
+ // show the popover
+ $container.popover('show');
+
+ // register mouse events on the popover
+ $target.next('.popover:not(.popover-initialized)')
+ .on({
+ 'mouseenter': function () {
+ $target.data('popover-hover', true);
+ },
+ 'mouseleave': function () {
+ hidePopover($container);
+ }
+ })
+ .addClass('popover-initialized');
+ },
+ 'mouseleave.popover': function () {
+ hidePopover($(this).children().first());
+ }
+ });
+ });
diff --git a/coverage/_js/jquery.min.js b/coverage/_js/jquery.min.js
new file mode 100644
index 00000000..2c69bc90
--- /dev/null
+++ b/coverage/_js/jquery.min.js
@@ -0,0 +1,2 @@
+/*! jQuery v3.6.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */
+!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,y=n.hasOwnProperty,a=y.toString,l=a.call(Object),v={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&v(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!y||!y.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ve(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ye(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ve(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],y=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML=" ",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||y.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||y.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||y.push(".#.+[+~]"),e.querySelectorAll("\\\f"),y.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML=" ";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),y=y.length&&new RegExp(y.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),v=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&v(p,e)?-1:t==C||t.ownerDocument==p&&v(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!y||!y.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),v.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",v.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML=" ",v.option=!!ce.lastChild;var ge={thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n",""]);var me=/<|?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),v.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(v.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return B(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=_e(v.pixelPosition,function(e,t){if(t)return t=Be(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return B(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0f&&(e=a.render.queue[f]);f++)d=e.generate(),typeof e.callback==typeof Function&&e.callback(d);a.render.queue.splice(0,f),a.render.queue.length?setTimeout(c):(a.dispatch.render_end(),a.render.active=!1)};setTimeout(c)},a.render.active=!1,a.render.queue=[],a.addGraph=function(b){typeof arguments[0]==typeof Function&&(b={generate:arguments[0],callback:arguments[1]}),a.render.queue.push(b),a.render.active||a.render()},"undefined"!=typeof module&&"undefined"!=typeof exports&&(module.exports=a),"undefined"!=typeof window&&(window.nv=a),a.dom.write=function(a){return void 0!==window.fastdom?fastdom.write(a):a()},a.dom.read=function(a){return void 0!==window.fastdom?fastdom.read(a):a()},a.interactiveGuideline=function(){"use strict";function b(l){l.each(function(l){function m(){var a=d3.mouse(this),d=a[0],e=a[1],i=!0,j=!1;if(k&&(d=d3.event.offsetX,e=d3.event.offsetY,"svg"!==d3.event.target.tagName&&(i=!1),d3.event.target.className.baseVal.match("nv-legend")&&(j=!0)),i&&(d-=f.left,e-=f.top),0>d||0>e||d>o||e>p||d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement||j){if(k&&d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement&&(void 0===d3.event.relatedTarget.className||d3.event.relatedTarget.className.match(c.nvPointerEventsClass)))return;return h.elementMouseout({mouseX:d,mouseY:e}),b.renderGuideLine(null),void c.hidden(!0)}c.hidden(!1);var l=g.invert(d);h.elementMousemove({mouseX:d,mouseY:e,pointXValue:l}),"dblclick"===d3.event.type&&h.elementDblclick({mouseX:d,mouseY:e,pointXValue:l}),"click"===d3.event.type&&h.elementClick({mouseX:d,mouseY:e,pointXValue:l})}var n=d3.select(this),o=d||960,p=e||400,q=n.selectAll("g.nv-wrap.nv-interactiveLineLayer").data([l]),r=q.enter().append("g").attr("class"," nv-wrap nv-interactiveLineLayer");r.append("g").attr("class","nv-interactiveGuideLine"),j&&(j.on("touchmove",m).on("mousemove",m,!0).on("mouseout",m,!0).on("dblclick",m).on("click",m),b.guideLine=null,b.renderGuideLine=function(c){i&&(b.guideLine&&b.guideLine.attr("x1")===c||a.dom.write(function(){var b=q.select(".nv-interactiveGuideLine").selectAll("line").data(null!=c?[a.utils.NaNtoZero(c)]:[],String);b.enter().append("line").attr("class","nv-guideline").attr("x1",function(a){return a}).attr("x2",function(a){return a}).attr("y1",p).attr("y2",0),b.exit().remove()}))})})}var c=a.models.tooltip();c.duration(0).hideDelay(0)._isInteractiveLayer(!0).hidden(!1);var d=null,e=null,f={left:0,top:0},g=d3.scale.linear(),h=d3.dispatch("elementMousemove","elementMouseout","elementClick","elementDblclick"),i=!0,j=null,k="ActiveXObject"in window;return b.dispatch=h,b.tooltip=c,b.margin=function(a){return arguments.length?(f.top="undefined"!=typeof a.top?a.top:f.top,f.left="undefined"!=typeof a.left?a.left:f.left,b):f},b.width=function(a){return arguments.length?(d=a,b):d},b.height=function(a){return arguments.length?(e=a,b):e},b.xScale=function(a){return arguments.length?(g=a,b):g},b.showGuideLine=function(a){return arguments.length?(i=a,b):i},b.svgContainer=function(a){return arguments.length?(j=a,b):j},b},a.interactiveBisect=function(a,b,c){"use strict";if(!(a instanceof Array))return null;var d;d="function"!=typeof c?function(a){return a.x}:c;var e=function(a,b){return d(a)-b},f=d3.bisector(e).left,g=d3.max([0,f(a,b)-1]),h=d(a[g]);if("undefined"==typeof h&&(h=g),h===b)return g;var i=d3.min([g+1,a.length-1]),j=d(a[i]);return"undefined"==typeof j&&(j=i),Math.abs(j-b)>=Math.abs(h-b)?g:i},a.nearestValueIndex=function(a,b,c){"use strict";var d=1/0,e=null;return a.forEach(function(a,f){var g=Math.abs(b-a);null!=a&&d>=g&&c>g&&(d=g,e=f)}),e},function(){"use strict";a.models.tooltip=function(){function b(){if(k){var a=d3.select(k);"svg"!==a.node().tagName&&(a=a.select("svg"));var b=a.node()?a.attr("viewBox"):null;if(b){b=b.split(" ");var c=parseInt(a.style("width"),10)/b[2];p.left=p.left*c,p.top=p.top*c}}}function c(){if(!n){var a;a=k?k:document.body,n=d3.select(a).append("div").attr("class","nvtooltip "+(j?j:"xy-tooltip")).attr("id",v),n.style("top",0).style("left",0),n.style("opacity",0),n.selectAll("div, table, td, tr").classed(w,!0),n.classed(w,!0),o=n.node()}}function d(){if(r&&B(e)){b();var f=p.left,g=null!==i?i:p.top;return a.dom.write(function(){c();var b=A(e);b&&(o.innerHTML=b),k&&u?a.dom.read(function(){var a=k.getElementsByTagName("svg")[0],b={left:0,top:0};if(a){var c=a.getBoundingClientRect(),d=k.getBoundingClientRect(),e=c.top;if(0>e){var i=k.getBoundingClientRect();e=Math.abs(e)>i.height?0:e}b.top=Math.abs(e-d.top),b.left=Math.abs(c.left-d.left)}f+=k.offsetLeft+b.left-2*k.scrollLeft,g+=k.offsetTop+b.top-2*k.scrollTop,h&&h>0&&(g=Math.floor(g/h)*h),C([f,g])}):C([f,g])}),d}}var e=null,f="w",g=25,h=0,i=null,j=null,k=null,l=!0,m=400,n=null,o=null,p={left:null,top:null},q={left:0,top:0},r=!0,s=100,t=!0,u=!1,v="nvtooltip-"+Math.floor(1e5*Math.random()),w="nv-pointer-events-none",x=function(a){return a},y=function(a){return a},z=function(a){return a},A=function(a){if(null===a)return"";var b=d3.select(document.createElement("table"));if(t){var c=b.selectAll("thead").data([a]).enter().append("thead");c.append("tr").append("td").attr("colspan",3).append("strong").classed("x-value",!0).html(y(a.value))}var d=b.selectAll("tbody").data([a]).enter().append("tbody"),e=d.selectAll("tr").data(function(a){return a.series}).enter().append("tr").classed("highlight",function(a){return a.highlight});e.append("td").classed("legend-color-guide",!0).append("div").style("background-color",function(a){return a.color}),e.append("td").classed("key",!0).html(function(a,b){return z(a.key,b)}),e.append("td").classed("value",!0).html(function(a,b){return x(a.value,b)}),e.selectAll("td").each(function(a){if(a.highlight){var b=d3.scale.linear().domain([0,1]).range(["#fff",a.color]),c=.6;d3.select(this).style("border-bottom-color",b(c)).style("border-top-color",b(c))}});var f=b.node().outerHTML;return void 0!==a.footer&&(f+=""),f},B=function(a){if(a&&a.series){if(a.series instanceof Array)return!!a.series.length;if(a.series instanceof Object)return a.series=[a.series],!0}return!1},C=function(b){o&&a.dom.read(function(){var c,d,e=parseInt(o.offsetHeight,10),h=parseInt(o.offsetWidth,10),i=a.utils.windowSize().width,j=a.utils.windowSize().height,k=window.pageYOffset,p=window.pageXOffset;j=window.innerWidth>=document.body.scrollWidth?j:j-16,i=window.innerHeight>=document.body.scrollHeight?i:i-16;var r,t,u=function(a){var b=d;do isNaN(a.offsetTop)||(b+=a.offsetTop),a=a.offsetParent;while(a);return b},v=function(a){var b=c;do isNaN(a.offsetLeft)||(b+=a.offsetLeft),a=a.offsetParent;while(a);return b};switch(f){case"e":c=b[0]-h-g,d=b[1]-e/2,r=v(o),t=u(o),p>r&&(c=b[0]+g>p?b[0]+g:p-r+c),k>t&&(d=k-t+d),t+e>k+j&&(d=k+j-t+d-e);break;case"w":c=b[0]+g,d=b[1]-e/2,r=v(o),t=u(o),r+h>i&&(c=b[0]-h-g),k>t&&(d=k+5),t+e>k+j&&(d=k+j-t+d-e);break;case"n":c=b[0]-h/2-5,d=b[1]+g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),t+e>k+j&&(d=k+j-t+d-e);break;case"s":c=b[0]-h/2,d=b[1]-e-g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),k>t&&(d=k);break;case"none":c=b[0],d=b[1]-g,r=v(o),t=u(o)}c-=q.left,d-=q.top;var w=o.getBoundingClientRect(),k=window.pageYOffset||document.documentElement.scrollTop,p=window.pageXOffset||document.documentElement.scrollLeft,x="translate("+(w.left+p)+"px, "+(w.top+k)+"px)",y="translate("+c+"px, "+d+"px)",z=d3.interpolateString(x,y),A=n.style("opacity")<.1;l?n.transition().delay(m).duration(0).style("opacity",0):n.interrupt().transition().duration(A?0:s).styleTween("transform",function(){return z},"important").style("-webkit-transform",y).style("opacity",1)})};return d.nvPointerEventsClass=w,d.options=a.utils.optionsFunc.bind(d),d._options=Object.create({},{duration:{get:function(){return s},set:function(a){s=a}},gravity:{get:function(){return f},set:function(a){f=a}},distance:{get:function(){return g},set:function(a){g=a}},snapDistance:{get:function(){return h},set:function(a){h=a}},classes:{get:function(){return j},set:function(a){j=a}},chartContainer:{get:function(){return k},set:function(a){k=a}},fixedTop:{get:function(){return i},set:function(a){i=a}},enabled:{get:function(){return r},set:function(a){r=a}},hideDelay:{get:function(){return m},set:function(a){m=a}},contentGenerator:{get:function(){return A},set:function(a){A=a}},valueFormatter:{get:function(){return x},set:function(a){x=a}},headerFormatter:{get:function(){return y},set:function(a){y=a}},keyFormatter:{get:function(){return z},set:function(a){z=a}},headerEnabled:{get:function(){return t},set:function(a){t=a}},_isInteractiveLayer:{get:function(){return u},set:function(a){u=!!a}},position:{get:function(){return p},set:function(a){p.left=void 0!==a.left?a.left:p.left,p.top=void 0!==a.top?a.top:p.top}},offset:{get:function(){return q},set:function(a){q.left=void 0!==a.left?a.left:q.left,q.top=void 0!==a.top?a.top:q.top}},hidden:{get:function(){return l},set:function(a){l!=a&&(l=!!a,d())}},data:{get:function(){return e},set:function(a){a.point&&(a.value=a.point.x,a.series=a.series||{},a.series.value=a.point.y,a.series.color=a.point.color||a.series.color),e=a}},tooltipElem:{get:function(){return o},set:function(){}},id:{get:function(){return v},set:function(){}}}),a.utils.initOptions(d),d}}(),a.utils.windowSize=function(){var a={width:640,height:480};return window.innerWidth&&window.innerHeight?(a.width=window.innerWidth,a.height=window.innerHeight,a):"CSS1Compat"==document.compatMode&&document.documentElement&&document.documentElement.offsetWidth?(a.width=document.documentElement.offsetWidth,a.height=document.documentElement.offsetHeight,a):document.body&&document.body.offsetWidth?(a.width=document.body.offsetWidth,a.height=document.body.offsetHeight,a):a},a.utils.windowResize=function(b){return window.addEventListener?window.addEventListener("resize",b):a.log("ERROR: Failed to bind to window.resize with: ",b),{callback:b,clear:function(){window.removeEventListener("resize",b)}}},a.utils.getColor=function(b){if(void 0===b)return a.utils.defaultColor();if(Array.isArray(b)){var c=d3.scale.ordinal().range(b);return function(a,b){var d=void 0===b?a:b;return a.color||c(d)}}return b},a.utils.defaultColor=function(){return a.utils.getColor(d3.scale.category20().range())},a.utils.customTheme=function(a,b,c){b=b||function(a){return a.key},c=c||d3.scale.category20().range();var d=c.length;return function(e){var f=b(e);return"function"==typeof a[f]?a[f]():void 0!==a[f]?a[f]:(d||(d=c.length),d-=1,c[d])}},a.utils.pjax=function(b,c){var d=function(d){d3.html(d,function(d){var e=d3.select(c).node();e.parentNode.replaceChild(d3.select(d).select(c).node(),e),a.utils.pjax(b,c)})};d3.selectAll(b).on("click",function(){history.pushState(this.href,this.textContent,this.href),d(this.href),d3.event.preventDefault()}),d3.select(window).on("popstate",function(){d3.event.state&&d(d3.event.state)})},a.utils.calcApproxTextWidth=function(a){if("function"==typeof a.style&&"function"==typeof a.text){var b=parseInt(a.style("font-size").replace("px",""),10),c=a.text().length;return c*b*.5}return 0},a.utils.NaNtoZero=function(a){return"number"!=typeof a||isNaN(a)||null===a||1/0===a||a===-1/0?0:a},d3.selection.prototype.watchTransition=function(a){var b=[this].concat([].slice.call(arguments,1));return a.transition.apply(a,b)},a.utils.renderWatch=function(b,c){if(!(this instanceof a.utils.renderWatch))return new a.utils.renderWatch(b,c);var d=void 0!==c?c:250,e=[],f=this;this.models=function(a){return a=[].slice.call(arguments,0),a.forEach(function(a){a.__rendered=!1,function(a){a.dispatch.on("renderEnd",function(){a.__rendered=!0,f.renderEnd("model")})}(a),e.indexOf(a)<0&&e.push(a)}),this},this.reset=function(a){void 0!==a&&(d=a),e=[]},this.transition=function(a,b,c){if(b=arguments.length>1?[].slice.call(arguments,1):[],c=b.length>1?b.pop():void 0!==d?d:250,a.__rendered=!1,e.indexOf(a)<0&&e.push(a),0===c)return a.__rendered=!0,a.delay=function(){return this},a.duration=function(){return this},a;a.__rendered=0===a.length?!0:a.every(function(a){return!a.length})?!0:!1;var g=0;return a.transition().duration(c).each(function(){++g}).each("end",function(){0===--g&&(a.__rendered=!0,f.renderEnd.apply(this,b))})},this.renderEnd=function(){e.every(function(a){return a.__rendered})&&(e.forEach(function(a){a.__rendered=!1}),b.renderEnd.apply(this,arguments))}},a.utils.deepExtend=function(b){var c=arguments.length>1?[].slice.call(arguments,1):[];c.forEach(function(c){for(var d in c){var e=b[d]instanceof Array,f="object"==typeof b[d],g="object"==typeof c[d];f&&!e&&g?a.utils.deepExtend(b[d],c[d]):b[d]=c[d]}})},a.utils.state=function(){if(!(this instanceof a.utils.state))return new a.utils.state;var b={},c=function(){},d=function(){return{}},e=null,f=null;this.dispatch=d3.dispatch("change","set"),this.dispatch.on("set",function(a){c(a,!0)}),this.getter=function(a){return d=a,this},this.setter=function(a,b){return b||(b=function(){}),c=function(c,d){a(c),d&&b()},this},this.init=function(b){e=e||{},a.utils.deepExtend(e,b)};var g=function(){var a=d();if(JSON.stringify(a)===JSON.stringify(b))return!1;for(var c in a)void 0===b[c]&&(b[c]={}),b[c]=a[c],f=!0;return!0};this.update=function(){e&&(c(e,!1),e=null),g.call(this)&&this.dispatch.change(b)}},a.utils.optionsFunc=function(a){return a&&d3.map(a).forEach(function(a,b){"function"==typeof this[a]&&this[a](b)}.bind(this)),this},a.utils.calcTicksX=function(b,c){var d=1,e=0;for(e;ed?f:d}return a.log("Requested number of ticks: ",b),a.log("Calculated max values to be: ",d),b=b>d?b=d-1:b,b=1>b?1:b,b=Math.floor(b),a.log("Calculating tick count as: ",b),b},a.utils.calcTicksY=function(b,c){return a.utils.calcTicksX(b,c)},a.utils.initOption=function(a,b){a._calls&&a._calls[b]?a[b]=a._calls[b]:(a[b]=function(c){return arguments.length?(a._overrides[b]=!0,a._options[b]=c,a):a._options[b]},a["_"+b]=function(c){return arguments.length?(a._overrides[b]||(a._options[b]=c),a):a._options[b]})},a.utils.initOptions=function(b){b._overrides=b._overrides||{};var c=Object.getOwnPropertyNames(b._options||{}),d=Object.getOwnPropertyNames(b._calls||{});c=c.concat(d);for(var e in c)a.utils.initOption(b,c[e])},a.utils.inheritOptionsD3=function(a,b,c){a._d3options=c.concat(a._d3options||[]),c.unshift(b),c.unshift(a),d3.rebind.apply(this,c)},a.utils.arrayUnique=function(a){return a.sort().filter(function(b,c){return!c||b!=a[c-1]})},a.utils.symbolMap=d3.map(),a.utils.symbol=function(){function b(b,e){var f=c.call(this,b,e),g=d.call(this,b,e);return-1!==d3.svg.symbolTypes.indexOf(f)?d3.svg.symbol().type(f).size(g)():a.utils.symbolMap.get(f)(g)}var c,d=64;return b.type=function(a){return arguments.length?(c=d3.functor(a),b):c},b.size=function(a){return arguments.length?(d=d3.functor(a),b):d},b},a.utils.inheritOptions=function(b,c){var d=Object.getOwnPropertyNames(c._options||{}),e=Object.getOwnPropertyNames(c._calls||{}),f=c._inherited||[],g=c._d3options||[],h=d.concat(e).concat(f).concat(g);h.unshift(c),h.unshift(b),d3.rebind.apply(this,h),b._inherited=a.utils.arrayUnique(d.concat(e).concat(f).concat(d).concat(b._inherited||[])),b._d3options=a.utils.arrayUnique(g.concat(b._d3options||[]))},a.utils.initSVG=function(a){a.classed({"nvd3-svg":!0})},a.utils.sanitizeHeight=function(a,b){return a||parseInt(b.style("height"),10)||400},a.utils.sanitizeWidth=function(a,b){return a||parseInt(b.style("width"),10)||960},a.utils.availableHeight=function(b,c,d){return a.utils.sanitizeHeight(b,c)-d.top-d.bottom},a.utils.availableWidth=function(b,c,d){return a.utils.sanitizeWidth(b,c)-d.left-d.right},a.utils.noData=function(b,c){var d=b.options(),e=d.margin(),f=d.noData(),g=null==f?["No Data Available."]:[f],h=a.utils.availableHeight(d.height(),c,e),i=a.utils.availableWidth(d.width(),c,e),j=e.left+i/2,k=e.top+h/2;c.selectAll("g").remove();var l=c.selectAll(".nv-noData").data(g);l.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),l.attr("x",j).attr("y",k).text(function(a){return a})},a.models.axis=function(){"use strict";function b(g){return s.reset(),g.each(function(b){var g=d3.select(this);a.utils.initSVG(g);var p=g.selectAll("g.nv-wrap.nv-axis").data([b]),q=p.enter().append("g").attr("class","nvd3 nv-wrap nv-axis"),t=(q.append("g"),p.select("g"));null!==n?c.ticks(n):("top"==c.orient()||"bottom"==c.orient())&&c.ticks(Math.abs(d.range()[1]-d.range()[0])/100),t.watchTransition(s,"axis").call(c),r=r||c.scale();var u=c.tickFormat();null==u&&(u=r.tickFormat());var v=t.selectAll("text.nv-axislabel").data([h||null]);v.exit().remove();var w,x,y;switch(c.orient()){case"top":v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",0).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b))+",0)"}).select("text").attr("dy","-0.5em").attr("y",-c.tickPadding()).attr("text-anchor","middle").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max top").attr("transform",function(b,c){return"translate("+a.utils.NaNtoZero(d.range()[c])+",0)"}));break;case"bottom":w=o+36;var z=30,A=0,B=t.selectAll("g").select("text"),C="";if(j%360){B.each(function(){var a=this.getBoundingClientRect(),b=a.width;A=a.height,b>z&&(z=b)}),C="rotate("+j+" 0,"+(A/2+c.tickPadding())+")";var D=Math.abs(Math.sin(j*Math.PI/180));w=(D?D*z:z)+30,B.attr("transform",C).style("text-anchor",j%360>0?"start":"end")}v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",w).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data([d.domain()[0],d.domain()[d.domain().length-1]]),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"}).select("text").attr("dy",".71em").attr("y",c.tickPadding()).attr("transform",C).style("text-anchor",j?j%360>0?"start":"end":"middle").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max bottom").attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"})),l&&B.attr("transform",function(a,b){return"translate(0,"+(b%2==0?"0":"12")+")"});break;case"right":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"begin").attr("transform",k?"rotate(90)":"").attr("y",k?-Math.max(e.right,f)+12:-10).attr("x",k?d3.max(d.range())/2:c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b){return"translate(0,"+a.utils.NaNtoZero(d(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",c.tickPadding()).style("text-anchor","start").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1));break;case"left":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"end").attr("transform",k?"rotate(-90)":"").attr("y",k?-Math.max(e.left,f)+25-(o||0):-10).attr("x",k?-d3.max(d.range())/2:-c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b){return"translate(0,"+a.utils.NaNtoZero(r(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",-c.tickPadding()).attr("text-anchor","end").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1))}if(v.text(function(a){return a}),!i||"left"!==c.orient()&&"right"!==c.orient()||(t.selectAll("g").each(function(a){d3.select(this).select("text").attr("opacity",1),(d(a)d.range()[0]-10)&&((a>1e-10||-1e-10>a)&&d3.select(this).attr("opacity",0),d3.select(this).select("text").attr("opacity",0))}),d.domain()[0]==d.domain()[1]&&0==d.domain()[0]&&p.selectAll("g.nv-axisMaxMin").style("opacity",function(a,b){return b?0:1})),i&&("top"===c.orient()||"bottom"===c.orient())){var E=[];p.selectAll("g.nv-axisMaxMin").each(function(a,b){try{E.push(b?d(a)-this.getBoundingClientRect().width-4:d(a)+this.getBoundingClientRect().width+4)}catch(c){E.push(b?d(a)-4:d(a)+4)}}),t.selectAll("g").each(function(a){(d(a)E[1])&&(a>1e-10||-1e-10>a?d3.select(this).remove():d3.select(this).select("text").remove())})}t.selectAll(".tick").filter(function(a){return!parseFloat(Math.round(1e5*a)/1e6)&&void 0!==a}).classed("zero",!0),r=d.copy()}),s.renderEnd("axis immediate"),b}var c=d3.svg.axis(),d=d3.scale.linear(),e={top:0,right:0,bottom:0,left:0},f=75,g=60,h=null,i=!0,j=0,k=!0,l=!1,m=!1,n=null,o=0,p=250,q=d3.dispatch("renderEnd");c.scale(d).orient("bottom").tickFormat(function(a){return a});var r,s=a.utils.renderWatch(q,p);return b.axis=c,b.dispatch=q,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{axisLabelDistance:{get:function(){return o},set:function(a){o=a}},staggerLabels:{get:function(){return l},set:function(a){l=a}},rotateLabels:{get:function(){return j},set:function(a){j=a}},rotateYLabel:{get:function(){return k},set:function(a){k=a}},showMaxMin:{get:function(){return i},set:function(a){i=a}},axisLabel:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return g},set:function(a){g=a}},ticks:{get:function(){return n},set:function(a){n=a}},width:{get:function(){return f},set:function(a){f=a}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},duration:{get:function(){return p},set:function(a){p=a,s.reset(p)}},scale:{get:function(){return d},set:function(e){d=e,c.scale(d),m="function"==typeof d.rangeBands,a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"])}}}),a.utils.initOptions(b),a.utils.inheritOptionsD3(b,c,["orient","tickValues","tickSubdivide","tickSize","tickPadding","tickFormat"]),a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"]),b},a.models.boxPlot=function(){"use strict";function b(l){return v.reset(),l.each(function(b){var l=j-i.left-i.right,p=k-i.top-i.bottom;r=d3.select(this),a.utils.initSVG(r),m.domain(c||b.map(function(a,b){return o(a,b)})).rangeBands(e||[0,l],.1);var w=[];if(!d){var x=d3.min(b.map(function(a){var b=[];return b.push(a.values.Q1),a.values.hasOwnProperty("whisker_low")&&null!==a.values.whisker_low&&b.push(a.values.whisker_low),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.min(b)})),y=d3.max(b.map(function(a){var b=[];return b.push(a.values.Q3),a.values.hasOwnProperty("whisker_high")&&null!==a.values.whisker_high&&b.push(a.values.whisker_high),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.max(b)}));w=[x,y]}n.domain(d||w),n.range(f||[p,0]),g=g||m,h=h||n.copy().range([n(0),n(0)]);{var z=r.selectAll("g.nv-wrap").data([b]);z.enter().append("g").attr("class","nvd3 nv-wrap")}z.attr("transform","translate("+i.left+","+i.top+")");var A=z.selectAll(".nv-boxplot").data(function(a){return a}),B=A.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);A.attr("class","nv-boxplot").attr("transform",function(a,b){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}).classed("hover",function(a){return a.hover}),A.watchTransition(v,"nv-boxplot: boxplots").style("stroke-opacity",1).style("fill-opacity",.75).delay(function(a,c){return c*t/b.length}).attr("transform",function(a,b){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}),A.exit().remove(),B.each(function(a,b){var c=d3.select(this);["low","high"].forEach(function(d){a.values.hasOwnProperty("whisker_"+d)&&null!==a.values["whisker_"+d]&&(c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-whisker nv-boxplot-"+d),c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-tick nv-boxplot-"+d))})});var C=A.selectAll(".nv-boxplot-outlier").data(function(a){return a.values.hasOwnProperty("outliers")&&null!==a.values.outliers?a.values.outliers:[]});C.enter().append("circle").style("fill",function(a,b,c){return q(a,c)}).style("stroke",function(a,b,c){return q(a,c)}).on("mouseover",function(a,b,c){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:a,color:q(a,c)},e:d3.event})}).on("mouseout",function(a,b,c){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:a,color:q(a,c)},e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})}),C.attr("class","nv-boxplot-outlier"),C.watchTransition(v,"nv-boxplot: nv-boxplot-outlier").attr("cx",.45*m.rangeBand()).attr("cy",function(a){return n(a)}).attr("r","3"),C.exit().remove();var D=function(){return null===u?.9*m.rangeBand():Math.min(75,.9*m.rangeBand())},E=function(){return.45*m.rangeBand()-D()/2},F=function(){return.45*m.rangeBand()+D()/2};["low","high"].forEach(function(a){var b="low"===a?"Q1":"Q3";A.select("line.nv-boxplot-whisker.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",.45*m.rangeBand()).attr("y1",function(b){return n(b.values["whisker_"+a])}).attr("x2",.45*m.rangeBand()).attr("y2",function(a){return n(a.values[b])}),A.select("line.nv-boxplot-tick.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",E).attr("y1",function(b){return n(b.values["whisker_"+a])}).attr("x2",F).attr("y2",function(b){return n(b.values["whisker_"+a])})}),["low","high"].forEach(function(a){B.selectAll(".nv-boxplot-"+a).on("mouseover",function(b,c,d){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mouseout",function(b,c,d){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})})}),B.append("rect").attr("class","nv-boxplot-box").on("mouseover",function(a,b){d3.select(this).classed("hover",!0),s.elementMouseover({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),s.elementMouseout({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})}),A.select("rect.nv-boxplot-box").watchTransition(v,"nv-boxplot: boxes").attr("y",function(a){return n(a.values.Q3)}).attr("width",D).attr("x",E).attr("height",function(a){return Math.abs(n(a.values.Q3)-n(a.values.Q1))||1}).style("fill",function(a,b){return a.color||q(a,b)}).style("stroke",function(a,b){return a.color||q(a,b)}),B.append("line").attr("class","nv-boxplot-median"),A.select("line.nv-boxplot-median").watchTransition(v,"nv-boxplot: boxplots line").attr("x1",E).attr("y1",function(a){return n(a.values.Q2)}).attr("x2",F).attr("y2",function(a){return n(a.values.Q2)}),g=m.copy(),h=n.copy()}),v.renderEnd("nv-boxplot immediate"),b}var c,d,e,f,g,h,i={top:0,right:0,bottom:0,left:0},j=960,k=500,l=Math.floor(1e4*Math.random()),m=d3.scale.ordinal(),n=d3.scale.linear(),o=function(a){return a.x},p=function(a){return a.y},q=a.utils.defaultColor(),r=null,s=d3.dispatch("elementMouseover","elementMouseout","elementMousemove","renderEnd"),t=250,u=null,v=a.utils.renderWatch(s,t);return b.dispatch=s,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},maxBoxWidth:{get:function(){return u},set:function(a){u=a}},x:{get:function(){return o},set:function(a){o=a}},y:{get:function(){return p},set:function(a){p=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return l},set:function(a){l=a}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}},duration:{get:function(){return t},set:function(a){t=a,v.reset(t)}}}),a.utils.initOptions(b),b},a.models.boxPlotChart=function(){"use strict";function b(k){return t.reset(),t.models(e),l&&t.models(f),m&&t.models(g),k.each(function(k){var p=d3.select(this);a.utils.initSVG(p);var t=(i||parseInt(p.style("width"))||960)-h.left-h.right,u=(j||parseInt(p.style("height"))||400)-h.top-h.bottom;if(b.update=function(){r.beforeUpdate(),p.transition().duration(s).call(b)},b.container=this,!(k&&k.length&&k.filter(function(a){return a.values.hasOwnProperty("Q1")&&a.values.hasOwnProperty("Q2")&&a.values.hasOwnProperty("Q3")}).length)){var v=p.selectAll(".nv-noData").data([q]);return v.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),v.attr("x",h.left+t/2).attr("y",h.top+u/2).text(function(a){return a}),b}p.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale().clamp(!0);var w=p.selectAll("g.nv-wrap.nv-boxPlotWithAxes").data([k]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-boxPlotWithAxes").append("g"),y=x.append("defs"),z=w.select("g");
+x.append("g").attr("class","nv-x nv-axis"),x.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),x.append("g").attr("class","nv-barsWrap"),z.attr("transform","translate("+h.left+","+h.top+")"),n&&z.select(".nv-y.nv-axis").attr("transform","translate("+t+",0)"),e.width(t).height(u);var A=z.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));if(A.transition().call(e),y.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"),z.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(o?2:1)).attr("height",16).attr("x",-c.rangeBand()/(o?1:2)),l){f.scale(c).ticks(a.utils.calcTicksX(t/100,k)).tickSize(-u,0),z.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),z.select(".nv-x.nv-axis").call(f);var B=z.select(".nv-x.nv-axis").selectAll("g");o&&B.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}m&&(g.scale(d).ticks(Math.floor(u/36)).tickSize(-t,0),z.select(".nv-y.nv-axis").call(g)),z.select(".nv-zeroLine line").attr("x1",0).attr("x2",t).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("nv-boxplot chart immediate"),b}var c,d,e=a.models.boxPlot(),f=a.models.axis(),g=a.models.axis(),h={top:15,right:10,bottom:50,left:60},i=null,j=null,k=a.utils.getColor(),l=!0,m=!0,n=!1,o=!1,p=a.models.tooltip(),q="No Data Available.",r=d3.dispatch("tooltipShow","tooltipHide","beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(n?"right":"left").tickFormat(d3.format(",.1f")),p.duration(0);var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){p.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(a){p.data(a).hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){p.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.boxplot=e,b.xAxis=f,b.yAxis=g,b.tooltip=p,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},staggerLabels:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return l},set:function(a){l=a}},showYAxis:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return tooltips},set:function(a){tooltips=a}},tooltipContent:{get:function(){return p},set:function(a){p=a}},noData:{get:function(){return q},set:function(a){q=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!==a.top?a.top:h.top,h.right=void 0!==a.right?a.right:h.right,h.bottom=void 0!==a.bottom?a.bottom:h.bottom,h.left=void 0!==a.left?a.left:h.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}},rightAlignYAxis:{get:function(){return n},set:function(a){n=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.bullet=function(){"use strict";function b(d){return d.each(function(b,d){var p=m-c.left-c.right,s=n-c.top-c.bottom;o=d3.select(this),a.utils.initSVG(o);{var t=f.call(this,b,d).slice().sort(d3.descending),u=g.call(this,b,d).slice().sort(d3.descending),v=h.call(this,b,d).slice().sort(d3.descending),w=i.call(this,b,d).slice(),x=j.call(this,b,d).slice(),y=k.call(this,b,d).slice(),z=d3.scale.linear().domain(d3.extent(d3.merge([l,t]))).range(e?[p,0]:[0,p]);this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range())}this.__chart__=z;var A=d3.min(t),B=d3.max(t),C=t[1],D=o.selectAll("g.nv-wrap.nv-bullet").data([b]),E=D.enter().append("g").attr("class","nvd3 nv-wrap nv-bullet"),F=E.append("g"),G=D.select("g");F.append("rect").attr("class","nv-range nv-rangeMax"),F.append("rect").attr("class","nv-range nv-rangeAvg"),F.append("rect").attr("class","nv-range nv-rangeMin"),F.append("rect").attr("class","nv-measure"),D.attr("transform","translate("+c.left+","+c.top+")");var H=function(a){return Math.abs(z(a)-z(0))},I=function(a){return z(0>a?a:0)};G.select("rect.nv-rangeMax").attr("height",s).attr("width",H(B>0?B:A)).attr("x",I(B>0?B:A)).datum(B>0?B:A),G.select("rect.nv-rangeAvg").attr("height",s).attr("width",H(C)).attr("x",I(C)).datum(C),G.select("rect.nv-rangeMin").attr("height",s).attr("width",H(B)).attr("x",I(B)).attr("width",H(B>0?A:B)).attr("x",I(B>0?A:B)).datum(B>0?A:B),G.select("rect.nv-measure").style("fill",q).attr("height",s/3).attr("y",s/3).attr("width",0>v?z(0)-z(v[0]):z(v[0])-z(0)).attr("x",I(v)).on("mouseover",function(){r.elementMouseover({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mouseout",function(){r.elementMouseout({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})});var J=s/6,K=u.map(function(a,b){return{value:a,label:x[b]}});F.selectAll("path.nv-markerTriangle").data(K).enter().append("path").attr("class","nv-markerTriangle").attr("transform",function(a){return"translate("+z(a.value)+","+s/2+")"}).attr("d","M0,"+J+"L"+J+","+-J+" "+-J+","+-J+"Z").on("mouseover",function(a){r.elementMouseover({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill"),pos:[z(a.value),s/2]})}).on("mousemove",function(a){r.elementMousemove({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a){r.elementMouseout({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}),D.selectAll(".nv-range").on("mouseover",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseover({value:a,label:c,color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseout({value:a,label:c,color:d3.select(this).style("fill")})})}),b}var c={top:0,right:0,bottom:0,left:0},d="left",e=!1,f=function(a){return a.ranges},g=function(a){return a.markers?a.markers:[0]},h=function(a){return a.measures},i=function(a){return a.rangeLabels?a.rangeLabels:[]},j=function(a){return a.markerLabels?a.markerLabels:[]},k=function(a){return a.measureLabels?a.measureLabels:[]},l=[0],m=380,n=30,o=null,p=null,q=a.utils.getColor(["#1f77b4"]),r=d3.dispatch("elementMouseover","elementMouseout","elementMousemove");return b.dispatch=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return f},set:function(a){f=a}},markers:{get:function(){return g},set:function(a){g=a}},measures:{get:function(){return h},set:function(a){h=a}},forceX:{get:function(){return l},set:function(a){l=a}},width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},tickFormat:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},orient:{get:function(){return d},set:function(a){d=a,e="right"==d||"bottom"==d}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.bulletChart=function(){"use strict";function b(d){return d.each(function(e,o){var p=d3.select(this);a.utils.initSVG(p);var q=a.utils.availableWidth(k,p,g),r=l-g.top-g.bottom;if(b.update=function(){b(d)},b.container=this,!e||!h.call(this,e,o))return a.utils.noData(b,p),b;p.selectAll(".nv-noData").remove();var s=h.call(this,e,o).slice().sort(d3.descending),t=i.call(this,e,o).slice().sort(d3.descending),u=j.call(this,e,o).slice().sort(d3.descending),v=p.selectAll("g.nv-wrap.nv-bulletChart").data([e]),w=v.enter().append("g").attr("class","nvd3 nv-wrap nv-bulletChart"),x=w.append("g"),y=v.select("g");x.append("g").attr("class","nv-bulletWrap"),x.append("g").attr("class","nv-titles"),v.attr("transform","translate("+g.left+","+g.top+")");var z=d3.scale.linear().domain([0,Math.max(s[0],t[0],u[0])]).range(f?[q,0]:[0,q]),A=this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range());this.__chart__=z;var B=x.select(".nv-titles").append("g").attr("text-anchor","end").attr("transform","translate(-6,"+(l-g.top-g.bottom)/2+")");B.append("text").attr("class","nv-title").text(function(a){return a.title}),B.append("text").attr("class","nv-subtitle").attr("dy","1em").text(function(a){return a.subtitle}),c.width(q).height(r);var C=y.select(".nv-bulletWrap");d3.transition(C).call(c);var D=m||z.tickFormat(q/100),E=y.selectAll("g.nv-tick").data(z.ticks(n?n:q/50),function(a){return this.textContent||D(a)}),F=E.enter().append("g").attr("class","nv-tick").attr("transform",function(a){return"translate("+A(a)+",0)"}).style("opacity",1e-6);F.append("line").attr("y1",r).attr("y2",7*r/6),F.append("text").attr("text-anchor","middle").attr("dy","1em").attr("y",7*r/6).text(D);var G=d3.transition(E).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1);G.select("line").attr("y1",r).attr("y2",7*r/6),G.select("text").attr("y",7*r/6),d3.transition(E.exit()).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1e-6).remove()}),d3.timer.flush(),b}var c=a.models.bullet(),d=a.models.tooltip(),e="left",f=!1,g={top:5,right:40,bottom:20,left:120},h=function(a){return a.ranges},i=function(a){return a.markers?a.markers:[0]},j=function(a){return a.measures},k=null,l=55,m=null,n=null,o=null,p=d3.dispatch("tooltipShow","tooltipHide");return d.duration(0).headerEnabled(!1),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.label,value:a.value,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.bullet=c,b.dispatch=p,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return h},set:function(a){h=a}},markers:{get:function(){return i},set:function(a){i=a}},measures:{get:function(){return j},set:function(a){j=a}},width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},tickFormat:{get:function(){return m},set:function(a){m=a}},ticks:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return o},set:function(a){o=a}},tooltips:{get:function(){return d.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),d.enabled(!!b)}},tooltipContent:{get:function(){return d.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),d.contentGenerator(b)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},orient:{get:function(){return e},set:function(a){e=a,f="right"==e||"bottom"==e}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.candlestickBar=function(){"use strict";function b(x){return x.each(function(b){c=d3.select(this);var x=a.utils.availableWidth(i,c,h),y=a.utils.availableHeight(j,c,h);a.utils.initSVG(c);var A=x/b[0].values.length*.45;l.domain(d||d3.extent(b[0].values.map(n).concat(t))),l.range(v?f||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]:f||[5+A/2,x-A/2-5]),m.domain(e||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(g||[y,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var B=d3.select(this).selectAll("g.nv-wrap.nv-candlestickBar").data([b[0].values]),C=B.enter().append("g").attr("class","nvd3 nv-wrap nv-candlestickBar"),D=C.append("defs"),E=C.append("g"),F=B.select("g");E.append("g").attr("class","nv-ticks"),B.attr("transform","translate("+h.left+","+h.top+")"),c.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:k})}),D.append("clipPath").attr("id","nv-chart-clip-path-"+k).append("rect"),B.select("#nv-chart-clip-path-"+k+" rect").attr("width",x).attr("height",y),F.attr("clip-path",w?"url(#nv-chart-clip-path-"+k+")":"");var G=B.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});G.exit().remove();{var H=G.enter().append("g").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b});H.append("line").attr("class","nv-candlestick-lines").attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),H.append("rect").attr("class","nv-candlestick-rects nv-bars").attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)})}c.selectAll(".nv-candlestick-lines").transition().attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),c.selectAll(".nv-candlestick-rects").transition().attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)})}),b}var c,d,e,f,g,h={top:0,right:0,bottom:0,left:0},i=null,j=null,k=Math.floor(1e4*Math.random()),l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,d){b.clearHighlights(),c.select(".nv-candlestickBar .nv-tick-0-"+a).classed("hover",d)},b.clearHighlights=function(){c.select(".nv-candlestickBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return k},set:function(a){k=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!=a.top?a.top:h.top,h.right=void 0!=a.right?a.right:h.right,h.bottom=void 0!=a.bottom?a.bottom:h.bottom,h.left=void 0!=a.left?a.left:h.left}},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.cumulativeLineChart=function(){"use strict";function b(l){return H.reset(),H.models(f),r&&H.models(g),s&&H.models(h),l.each(function(l){function A(){d3.select(b.container).style("cursor","ew-resize")}function E(){G.x=d3.event.x,G.i=Math.round(F.invert(G.x)),K()}function H(){d3.select(b.container).style("cursor","auto"),y.index=G.i,C.stateChange(y)}function K(){bb.data([G]);var a=b.duration();b.duration(0),b.update(),b.duration(a)}var L=d3.select(this);a.utils.initSVG(L),L.classed("nv-chart-"+x,!0);var M=this,N=a.utils.availableWidth(o,L,m),O=a.utils.availableHeight(p,L,m);if(b.update=function(){0===D?L.call(b):L.transition().duration(D).call(b)},b.container=this,y.setter(J(l),b.update).getter(I(l)).update(),y.disabled=l.map(function(a){return!!a.disabled}),!z){var P;z={};for(P in y)z[P]=y[P]instanceof Array?y[P].slice(0):y[P]}var Q=d3.behavior.drag().on("dragstart",A).on("drag",E).on("dragend",H);if(!(l&&l.length&&l.filter(function(a){return a.values.length}).length))return a.utils.noData(b,L),b;if(L.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale(),w)f.yDomain(null);else{var R=l.filter(function(a){return!a.disabled}).map(function(a){var b=d3.extent(a.values,f.y());return b[0]<-.95&&(b[0]=-.95),[(b[0]-b[1])/(1+b[1]),(b[1]-b[0])/(1+b[0])]}),S=[d3.min(R,function(a){return a[0]}),d3.max(R,function(a){return a[1]})];f.yDomain(S)}F.domain([0,l[0].values.length-1]).range([0,N]).clamp(!0);var l=c(G.i,l),T=v?"none":"all",U=L.selectAll("g.nv-wrap.nv-cumulativeLine").data([l]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-cumulativeLine").append("g"),W=U.select("g");if(V.append("g").attr("class","nv-interactive"),V.append("g").attr("class","nv-x nv-axis").style("pointer-events","none"),V.append("g").attr("class","nv-y nv-axis"),V.append("g").attr("class","nv-background"),V.append("g").attr("class","nv-linesWrap").style("pointer-events",T),V.append("g").attr("class","nv-avgLinesWrap").style("pointer-events","none"),V.append("g").attr("class","nv-legendWrap"),V.append("g").attr("class","nv-controlsWrap"),q&&(i.width(N),W.select(".nv-legendWrap").datum(l).call(i),m.top!=i.height()&&(m.top=i.height(),O=a.utils.availableHeight(p,L,m)),W.select(".nv-legendWrap").attr("transform","translate(0,"+-m.top+")")),u){var X=[{key:"Re-scale y-axis",disabled:!w}];j.width(140).color(["#444","#444","#444"]).rightAlign(!1).margin({top:5,right:0,bottom:5,left:20}),W.select(".nv-controlsWrap").datum(X).attr("transform","translate(0,"+-m.top+")").call(j)}U.attr("transform","translate("+m.left+","+m.top+")"),t&&W.select(".nv-y.nv-axis").attr("transform","translate("+N+",0)");var Y=l.filter(function(a){return a.tempDisabled});U.select(".tempDisabled").remove(),Y.length&&U.append("text").attr("class","tempDisabled").attr("x",N/2).attr("y","-.71em").style("text-anchor","end").text(Y.map(function(a){return a.key}).join(", ")+" values cannot be calculated for this time period."),v&&(k.width(N).height(O).margin({left:m.left,top:m.top}).svgContainer(L).xScale(d),U.select(".nv-interactive").call(k)),V.select(".nv-background").append("rect"),W.select(".nv-background rect").attr("width",N).attr("height",O),f.y(function(a){return a.display.y}).width(N).height(O).color(l.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!l[b].disabled&&!l[b].tempDisabled}));var Z=W.select(".nv-linesWrap").datum(l.filter(function(a){return!a.disabled&&!a.tempDisabled}));Z.call(f),l.forEach(function(a,b){a.seriesIndex=b});var $=l.filter(function(a){return!a.disabled&&!!B(a)}),_=W.select(".nv-avgLinesWrap").selectAll("line").data($,function(a){return a.key}),ab=function(a){var b=e(B(a));return 0>b?0:b>O?O:b};_.enter().append("line").style("stroke-width",2).style("stroke-dasharray","10,10").style("stroke",function(a){return f.color()(a,a.seriesIndex)}).attr("x1",0).attr("x2",N).attr("y1",ab).attr("y2",ab),_.style("stroke-opacity",function(a){var b=e(B(a));return 0>b||b>O?0:1}).attr("x1",0).attr("x2",N).attr("y1",ab).attr("y2",ab),_.exit().remove();var bb=Z.selectAll(".nv-indexLine").data([G]);bb.enter().append("rect").attr("class","nv-indexLine").attr("width",3).attr("x",-2).attr("fill","red").attr("fill-opacity",.5).style("pointer-events","all").call(Q),bb.attr("transform",function(a){return"translate("+F(a.i)+",0)"}).attr("height",O),r&&(g.scale(d)._ticks(a.utils.calcTicksX(N/70,l)).tickSize(-O,0),W.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),W.select(".nv-x.nv-axis").call(g)),s&&(h.scale(e)._ticks(a.utils.calcTicksY(O/36,l)).tickSize(-N,0),W.select(".nv-y.nv-axis").call(h)),W.select(".nv-background rect").on("click",function(){G.x=d3.mouse(this)[0],G.i=Math.round(F.invert(G.x)),y.index=G.i,C.stateChange(y),K()}),f.dispatch.on("elementClick",function(a){G.i=a.pointIndex,G.x=F(G.i),y.index=G.i,C.stateChange(y),K()}),j.dispatch.on("legendClick",function(a){a.disabled=!a.disabled,w=!a.disabled,y.rescaleY=w,C.stateChange(y),b.update()}),i.dispatch.on("stateChange",function(a){for(var c in a)y[c]=a[c];C.stateChange(y),b.update()}),k.dispatch.on("elementMousemove",function(c){f.clearHighlights();var d,e,i,j=[];if(l.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g,h){e=a.interactiveBisect(g.values,c.pointXValue,b.x()),f.highlightPoint(h,e,!0);var k=g.values[e];"undefined"!=typeof k&&("undefined"==typeof d&&(d=k),"undefined"==typeof i&&(i=b.xScale()(b.x()(k,e))),j.push({key:g.key,value:b.y()(k,e),color:n(g,g.seriesIndex)}))}),j.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(j.map(function(a){return a.value}),o,q);null!==r&&(j[r].highlight=!0)}var s=g.tickFormat()(b.x()(d,e),e);k.tooltip.position({left:i+m.left,top:c.mouseY+m.top}).chartContainer(M.parentNode).valueFormatter(function(a){return h.tickFormat()(a)}).data({value:s,series:j})(),k.renderGuideLine(i)}),k.dispatch.on("elementMouseout",function(){f.clearHighlights()}),C.on("changeState",function(a){"undefined"!=typeof a.disabled&&(l.forEach(function(b,c){b.disabled=a.disabled[c]}),y.disabled=a.disabled),"undefined"!=typeof a.index&&(G.i=a.index,G.x=F(G.i),y.index=a.index,bb.data([G])),"undefined"!=typeof a.rescaleY&&(w=a.rescaleY),b.update()})}),H.renderEnd("cumulativeLineChart immediate"),b}function c(a,b){return K||(K=f.y()),b.map(function(b){if(!b.values)return b;var c=b.values[a];if(null==c)return b;var d=K(c,a);return-.95>d&&!E?(b.tempDisabled=!0,b):(b.tempDisabled=!1,b.values=b.values.map(function(a,b){return a.display={y:(K(a,b)-d)/(1+d)},a}),b)})}var d,e,f=a.models.line(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.models.legend(),k=a.interactiveGuideline(),l=a.models.tooltip(),m={top:30,right:30,bottom:50,left:60},n=a.utils.defaultColor(),o=null,p=null,q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=!0,x=f.id(),y=a.utils.state(),z=null,A=null,B=function(a){return a.average},C=d3.dispatch("stateChange","changeState","renderEnd"),D=250,E=!1;y.index=0,y.rescaleY=w,g.orient("bottom").tickPadding(7),h.orient(t?"right":"left"),l.valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)}),j.updateState(!1);var F=d3.scale.linear(),G={i:0,x:0},H=a.utils.renderWatch(C,D),I=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),index:G.i,rescaleY:w}}},J=function(a){return function(b){void 0!==b.index&&(G.i=b.index),void 0!==b.rescaleY&&(w=b.rescaleY),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};f.dispatch.on("elementMouseover.tooltip",function(a){var c={x:b.x()(a.point),y:b.y()(a.point),color:a.point.color};a.point=c,l.data(a).position(a.pos).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(){l.hidden(!0)});var K=null;return b.dispatch=C,b.lines=f,b.legend=i,b.controls=j,b.xAxis=g,b.yAxis=h,b.interactiveLayer=k,b.state=y,b.tooltip=l,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return o},set:function(a){o=a}},height:{get:function(){return p},set:function(a){p=a}},rescaleY:{get:function(){return w},set:function(a){w=a}},showControls:{get:function(){return u},set:function(a){u=a}},showLegend:{get:function(){return q},set:function(a){q=a}},average:{get:function(){return B},set:function(a){B=a}},defaultState:{get:function(){return z},set:function(a){z=a}},noData:{get:function(){return A},set:function(a){A=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},noErrorCheck:{get:function(){return E},set:function(a){E=a}},tooltips:{get:function(){return l.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),l.enabled(!!b)}},tooltipContent:{get:function(){return l.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),l.contentGenerator(b)}},margin:{get:function(){return m},set:function(a){m.top=void 0!==a.top?a.top:m.top,m.right=void 0!==a.right?a.right:m.right,m.bottom=void 0!==a.bottom?a.bottom:m.bottom,m.left=void 0!==a.left?a.left:m.left}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),i.color(n)}},useInteractiveGuideline:{get:function(){return v},set:function(a){v=a,a===!0&&(b.interactive(!1),b.useVoronoi(!1))}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,h.orient(a?"right":"left")}},duration:{get:function(){return D},set:function(a){D=a,f.duration(D),g.duration(D),h.duration(D),H.reset(D)}}}),a.utils.inheritOptions(b,f),a.utils.initOptions(b),b},a.models.discreteBar=function(){"use strict";function b(m){return y.reset(),m.each(function(b){var m=k-j.left-j.right,x=l-j.top-j.bottom;c=d3.select(this),a.utils.initSVG(c),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var z=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),y0:a.y0}})});n.domain(d||d3.merge(z).map(function(a){return a.x})).rangeBands(f||[0,m],.1),o.domain(e||d3.extent(d3.merge(z).map(function(a){return a.y}).concat(r))),o.range(t?g||[x-(o.domain()[0]<0?12:0),o.domain()[1]>0?12:0]:g||[x,0]),h=h||n,i=i||o.copy().range([o(0),o(0)]);{var A=c.selectAll("g.nv-wrap.nv-discretebar").data([b]),B=A.enter().append("g").attr("class","nvd3 nv-wrap nv-discretebar"),C=B.append("g");A.select("g")}C.append("g").attr("class","nv-groups"),A.attr("transform","translate("+j.left+","+j.top+")");var D=A.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});D.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),D.exit().watchTransition(y,"discreteBar: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),D.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),D.watchTransition(y,"discreteBar: groups").style("stroke-opacity",1).style("fill-opacity",.75);var E=D.selectAll("g.nv-bar").data(function(a){return a.values});E.exit().remove();var F=E.enter().append("g").attr("transform",function(a,b){return"translate("+(n(p(a,b))+.05*n.rangeBand())+", "+o(0)+")"}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),v.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),v.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){v.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){v.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()});F.append("rect").attr("height",0).attr("width",.9*n.rangeBand()/b.length),t?(F.append("text").attr("text-anchor","middle"),E.select("text").text(function(a,b){return u(q(a,b))}).watchTransition(y,"discreteBar: bars text").attr("x",.9*n.rangeBand()/2).attr("y",function(a,b){return q(a,b)<0?o(q(a,b))-o(0)+12:-4})):E.selectAll("text").remove(),E.attr("class",function(a,b){return q(a,b)<0?"nv-bar negative":"nv-bar positive"}).style("fill",function(a,b){return a.color||s(a,b)}).style("stroke",function(a,b){return a.color||s(a,b)}).select("rect").attr("class",w).watchTransition(y,"discreteBar: bars rect").attr("width",.9*n.rangeBand()/b.length),E.watchTransition(y,"discreteBar: bars").attr("transform",function(a,b){var c=n(p(a,b))+.05*n.rangeBand(),d=q(a,b)<0?o(0):o(0)-o(q(a,b))<1?o(0)-1:o(q(a,b));return"translate("+c+", "+d+")"}).select("rect").attr("height",function(a,b){return Math.max(Math.abs(o(q(a,b))-o(e&&e[0]||0))||1)}),h=n.copy(),i=o.copy()}),y.renderEnd("discreteBar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=d3.scale.ordinal(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=[0],s=a.utils.defaultColor(),t=!1,u=d3.format(",.2f"),v=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),w="discreteBar",x=250,y=a.utils.renderWatch(v,x);return b.dispatch=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},forceY:{get:function(){return r},set:function(a){r=a}},showValues:{get:function(){return t},set:function(a){t=a}},x:{get:function(){return p},set:function(a){p=a}},y:{get:function(){return q},set:function(a){q=a}},xScale:{get:function(){return n},set:function(a){n=a}},yScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},valueFormat:{get:function(){return u},set:function(a){u=a}},id:{get:function(){return m},set:function(a){m=a}},rectClass:{get:function(){return w},set:function(a){w=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b)}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x)}}}),a.utils.initOptions(b),b},a.models.discreteBarChart=function(){"use strict";function b(h){return t.reset(),t.models(e),m&&t.models(f),n&&t.models(g),h.each(function(h){var l=d3.select(this);a.utils.initSVG(l);var q=a.utils.availableWidth(j,l,i),t=a.utils.availableHeight(k,l,i);if(b.update=function(){r.beforeUpdate(),l.transition().duration(s).call(b)},b.container=this,!(h&&h.length&&h.filter(function(a){return a.values.length}).length))return a.utils.noData(b,l),b;l.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale().clamp(!0);var u=l.selectAll("g.nv-wrap.nv-discreteBarWithAxes").data([h]),v=u.enter().append("g").attr("class","nvd3 nv-wrap nv-discreteBarWithAxes").append("g"),w=v.append("defs"),x=u.select("g");v.append("g").attr("class","nv-x nv-axis"),v.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),v.append("g").attr("class","nv-barsWrap"),x.attr("transform","translate("+i.left+","+i.top+")"),o&&x.select(".nv-y.nv-axis").attr("transform","translate("+q+",0)"),e.width(q).height(t);var y=x.select(".nv-barsWrap").datum(h.filter(function(a){return!a.disabled}));if(y.transition().call(e),w.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"),x.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(p?2:1)).attr("height",16).attr("x",-c.rangeBand()/(p?1:2)),m){f.scale(c)._ticks(a.utils.calcTicksX(q/100,h)).tickSize(-t,0),x.select(".nv-x.nv-axis").attr("transform","translate(0,"+(d.range()[0]+(e.showValues()&&d.domain()[0]<0?16:0))+")"),x.select(".nv-x.nv-axis").call(f);
+var z=x.select(".nv-x.nv-axis").selectAll("g");p&&z.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}n&&(g.scale(d)._ticks(a.utils.calcTicksY(t/36,h)).tickSize(-q,0),x.select(".nv-y.nv-axis").call(g)),x.select(".nv-zeroLine line").attr("x1",0).attr("x2",q).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("discreteBar chart immediate"),b}var c,d,e=a.models.discreteBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.tooltip(),i={top:15,right:10,bottom:50,left:60},j=null,k=null,l=a.utils.getColor(),m=!0,n=!0,o=!1,p=!1,q=null,r=d3.dispatch("beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(o?"right":"left").tickFormat(d3.format(",.1f")),h.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).keyFormatter(function(a,b){return f.tickFormat()(a,b)});var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},h.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){h.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){h.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.discretebar=e,b.xAxis=f,b.yAxis=g,b.tooltip=h,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},staggerLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return m},set:function(a){m=a}},showYAxis:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return q},set:function(a){q=a}},tooltips:{get:function(){return h.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),h.enabled(!!b)}},tooltipContent:{get:function(){return h.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),h.contentGenerator(b)}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),e.color(l)}},rightAlignYAxis:{get:function(){return o},set:function(a){o=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.distribution=function(){"use strict";function b(k){return m.reset(),k.each(function(b){var k=(e-("x"===g?d.left+d.right:d.top+d.bottom),"x"==g?"y":"x"),l=d3.select(this);a.utils.initSVG(l),c=c||j;var n=l.selectAll("g.nv-distribution").data([b]),o=n.enter().append("g").attr("class","nvd3 nv-distribution"),p=(o.append("g"),n.select("g"));n.attr("transform","translate("+d.left+","+d.top+")");var q=p.selectAll("g.nv-dist").data(function(a){return a},function(a){return a.key});q.enter().append("g"),q.attr("class",function(a,b){return"nv-dist nv-series-"+b}).style("stroke",function(a,b){return i(a,b)});var r=q.selectAll("line.nv-dist"+g).data(function(a){return a.values});r.enter().append("line").attr(g+"1",function(a,b){return c(h(a,b))}).attr(g+"2",function(a,b){return c(h(a,b))}),m.transition(q.exit().selectAll("line.nv-dist"+g),"dist exit").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}).style("stroke-opacity",0).remove(),r.attr("class",function(a,b){return"nv-dist"+g+" nv-dist"+g+"-"+b}).attr(k+"1",0).attr(k+"2",f),m.transition(r,"dist").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}),c=j.copy()}),m.renderEnd("distribution immediate"),b}var c,d={top:0,right:0,bottom:0,left:0},e=400,f=8,g="x",h=function(a){return a[g]},i=a.utils.defaultColor(),j=d3.scale.linear(),k=250,l=d3.dispatch("renderEnd"),m=a.utils.renderWatch(l,k);return b.options=a.utils.optionsFunc.bind(b),b.dispatch=l,b.margin=function(a){return arguments.length?(d.top="undefined"!=typeof a.top?a.top:d.top,d.right="undefined"!=typeof a.right?a.right:d.right,d.bottom="undefined"!=typeof a.bottom?a.bottom:d.bottom,d.left="undefined"!=typeof a.left?a.left:d.left,b):d},b.width=function(a){return arguments.length?(e=a,b):e},b.axis=function(a){return arguments.length?(g=a,b):g},b.size=function(a){return arguments.length?(f=a,b):f},b.getData=function(a){return arguments.length?(h=d3.functor(a),b):h},b.scale=function(a){return arguments.length?(j=a,b):j},b.color=function(c){return arguments.length?(i=a.utils.getColor(c),b):i},b.duration=function(a){return arguments.length?(k=a,m.reset(k),b):k},b},a.models.furiousLegend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?g(a,b):"#fff":m?void 0:a.disabled?g(a,b):"#fff"}function r(a,b){return m&&"furious"==o?a.disengaged?"#fff":g(a,b):a.disabled?"#fff":g(a,b)}return p.each(function(b){var p=d-c.left-c.right,s=d3.select(this);a.utils.initSVG(s);var t=s.selectAll("g.nv-legend").data([b]),u=(t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),t.select("g"));t.attr("transform","translate("+c.left+","+c.top+")");var v,w=u.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),x=w.enter().append("g").attr("class","nv-series");if("classic"==o)x.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),v=w.select("circle");else if("furious"==o){x.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),v=w.select("rect"),x.append("g").attr("class","nv-check-box").property("innerHTML",' ').attr("transform","translate(-10,-8)scale(0.5)");var y=w.select(".nv-check-box");y.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}x.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var z=w.select("text.nv-legend-text");w.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=w.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=w.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),w.classed("nv-disabled",function(a){return a.userDisabled}),w.exit().remove(),z.attr("fill",q).text(f);var A;switch(o){case"furious":A=23;break;case"classic":A=20}if(h){var B=[];w.each(function(){var b,c=d3.select(this).select("text");try{if(b=c.node().getComputedTextLength(),0>=b)throw Error()}catch(d){b=a.utils.calcApproxTextWidth(c)}B.push(b+i)});for(var C=0,D=0,E=[];p>D&&Cp&&C>1;){E=[],C--;for(var F=0;F(E[F%C]||0)&&(E[F%C]=B[F]);D=E.reduce(function(a,b){return a+b})}for(var G=[],H=0,I=0;C>H;H++)G[H]=I,I+=E[H];w.attr("transform",function(a,b){return"translate("+G[b%C]+","+(5+Math.floor(b/C)*A)+")"}),j?u.attr("transform","translate("+(d-c.right-D)+","+c.top+")"):u.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(B.length/C)*A}else{var J,K=5,L=5,M=0;w.attr("transform",function(){var a=d3.select(this).select("text").node().getComputedTextLength()+i;return J=L,dM&&(M=L),"translate("+J+","+K+")"}),u.attr("transform","translate("+(d-c.right-M)+","+c.top+")"),e=c.top+c.bottom+K+15}"furious"==o&&v.attr("width",function(a,b){return z[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),v.style("fill",r).style("stroke",function(a,b){return a.color||g(a,b)})}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=28,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBar=function(){"use strict";function b(x){return x.each(function(b){w.reset(),k=d3.select(this);var x=a.utils.availableWidth(h,k,g),y=a.utils.availableHeight(i,k,g);a.utils.initSVG(k),l.domain(c||d3.extent(b[0].values.map(n).concat(p))),l.range(r?e||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]:e||[0,x]),m.domain(d||d3.extent(b[0].values.map(o).concat(q))).range(f||[y,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var z=k.selectAll("g.nv-wrap.nv-historicalBar-"+j).data([b[0].values]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBar-"+j),B=A.append("defs"),C=A.append("g"),D=z.select("g");C.append("g").attr("class","nv-bars"),z.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){u.chartClick({data:a,index:b,pos:d3.event,id:j})}),B.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),z.select("#nv-chart-clip-path-"+j+" rect").attr("width",x).attr("height",y),D.attr("clip-path",s?"url(#nv-chart-clip-path-"+j+")":"");var E=z.select(".nv-bars").selectAll(".nv-bar").data(function(a){return a},function(a,b){return n(a,b)});E.exit().remove(),E.enter().append("rect").attr("x",0).attr("y",function(b,c){return a.utils.NaNtoZero(m(Math.max(0,o(b,c))))}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.abs(m(o(b,c))-m(0)))}).attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).on("mouseover",function(a,b){v&&(d3.select(this).classed("hover",!0),u.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mouseout",function(a,b){v&&(d3.select(this).classed("hover",!1),u.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mousemove",function(a,b){v&&u.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v&&(u.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}).on("dblclick",function(a,b){v&&(u.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}),E.attr("fill",function(a,b){return t(a,b)}).attr("class",function(a,b,c){return(o(a,b)<0?"nv-bar negative":"nv-bar positive")+" nv-bar-"+c+"-"+b}).watchTransition(w,"bars").attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).attr("width",x/b[0].values.length*.9),E.watchTransition(w,"bars").attr("y",function(b,c){var d=o(b,c)<0?m(0):m(0)-m(o(b,c))<1?m(0)-1:m(o(b,c));return a.utils.NaNtoZero(d)}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.max(Math.abs(m(o(b,c))-m(0)),1))})}),w.renderEnd("historicalBar immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=[],q=[0],r=!1,s=!0,t=a.utils.defaultColor(),u=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),v=!0,w=a.utils.renderWatch(u,0);return b.highlightPoint=function(a,b){k.select(".nv-bars .nv-bar-0-"+a).classed("hover",b)},b.clearHighlights=function(){k.select(".nv-bars .nv-bar.hover").classed("hover",!1)},b.dispatch=u,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},forceX:{get:function(){return p},set:function(a){p=a}},forceY:{get:function(){return q},set:function(a){q=a}},padData:{get:function(){return r},set:function(a){r=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},clipEdge:{get:function(){return s},set:function(a){s=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return v},set:function(a){v=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return t},set:function(b){t=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBarChart=function(b){"use strict";function c(b){return b.each(function(k){z.reset(),z.models(f),q&&z.models(g),r&&z.models(h);var w=d3.select(this),A=this;a.utils.initSVG(w);var B=a.utils.availableWidth(n,w,l),C=a.utils.availableHeight(o,w,l);if(c.update=function(){w.transition().duration(y).call(c)},c.container=this,u.disabled=k.map(function(a){return!!a.disabled}),!v){var D;v={};for(D in u)v[D]=u[D]instanceof Array?u[D].slice(0):u[D]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(c,w),c;w.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale();var E=w.selectAll("g.nv-wrap.nv-historicalBarChart").data([k]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBarChart").append("g"),G=E.select("g");F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-barsWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),p&&(i.width(B),G.select(".nv-legendWrap").datum(k).call(i),l.top!=i.height()&&(l.top=i.height(),C=a.utils.availableHeight(o,w,l)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-l.top+")")),E.attr("transform","translate("+l.left+","+l.top+")"),s&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),t&&(j.width(B).height(C).margin({left:l.left,top:l.top}).svgContainer(w).xScale(d),E.select(".nv-interactive").call(j)),f.width(B).height(C).color(k.map(function(a,b){return a.color||m(a,b)}).filter(function(a,b){return!k[b].disabled}));var H=G.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));H.transition().call(f),q&&(g.scale(d)._ticks(a.utils.calcTicksX(B/100,k)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),G.select(".nv-x.nv-axis").transition().call(g)),r&&(h.scale(e)._ticks(a.utils.calcTicksY(C/36,k)).tickSize(-B,0),G.select(".nv-y.nv-axis").transition().call(h)),j.dispatch.on("elementMousemove",function(b){f.clearHighlights();var d,e,i,n=[];k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g){e=a.interactiveBisect(g.values,b.pointXValue,c.x()),f.highlightPoint(e,!0);var h=g.values[e];void 0!==h&&(void 0===d&&(d=h),void 0===i&&(i=c.xScale()(c.x()(h,e))),n.push({key:g.key,value:c.y()(h,e),color:m(g,g.seriesIndex),data:g.values[e]}))});var o=g.tickFormat()(c.x()(d,e));j.tooltip.position({left:i+l.left,top:b.mouseY+l.top}).chartContainer(A.parentNode).valueFormatter(function(a){return h.tickFormat()(a)}).data({value:o,index:e,series:n})(),j.renderGuideLine(i)}),j.dispatch.on("elementMouseout",function(){x.tooltipHide(),f.clearHighlights()}),i.dispatch.on("legendClick",function(a){a.disabled=!a.disabled,k.filter(function(a){return!a.disabled}).length||k.map(function(a){return a.disabled=!1,E.selectAll(".nv-series").classed("disabled",!1),a}),u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),b.transition().call(c)}),i.dispatch.on("legendDblclick",function(a){k.forEach(function(a){a.disabled=!0}),a.disabled=!1,u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),c.update()}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),c.update()})}),z.renderEnd("historicalBarChart immediate"),c}var d,e,f=b||a.models.historicalBar(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:90,bottom:50,left:90},m=a.utils.defaultColor(),n=null,o=null,p=!1,q=!0,r=!0,s=!1,t=!1,u={},v=null,w=null,x=d3.dispatch("tooltipHide","stateChange","changeState","renderEnd"),y=250;g.orient("bottom").tickPadding(7),h.orient(s?"right":"left"),k.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)});var z=a.utils.renderWatch(x,0);return f.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:c.x()(a.data),value:c.y()(a.data),color:a.color},k.data(a).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(){k.hidden(!0)}),f.dispatch.on("elementMousemove.tooltip",function(){k.position({top:d3.event.pageY,left:d3.event.pageX})()}),c.dispatch=x,c.bars=f,c.legend=i,c.xAxis=g,c.yAxis=h,c.interactiveLayer=j,c.tooltip=k,c.options=a.utils.optionsFunc.bind(c),c._options=Object.create({},{width:{get:function(){return n},set:function(a){n=a}},height:{get:function(){return o},set:function(a){o=a}},showLegend:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return q},set:function(a){q=a}},showYAxis:{get:function(){return r},set:function(a){r=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b),i.color(m),f.color(m)}},duration:{get:function(){return y},set:function(a){y=a,z.reset(y),h.duration(y),g.duration(y)}},rightAlignYAxis:{get:function(){return s},set:function(a){s=a,h.orient(a?"right":"left")}},useInteractiveGuideline:{get:function(){return t},set:function(a){t=a,a===!0&&c.interactive(!1)}}}),a.utils.inheritOptions(c,f),a.utils.initOptions(c),c},a.models.ohlcBarChart=function(){var b=a.models.historicalBarChart(a.models.ohlcBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open'+a.value+"open: "+b.yAxis.tickFormat()(c.open)+" close: "+b.yAxis.tickFormat()(c.close)+" high "+b.yAxis.tickFormat()(c.high)+" low: "+b.yAxis.tickFormat()(c.low)+"
"}),b},a.models.candlestickBarChart=function(){var b=a.models.historicalBarChart(a.models.candlestickBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open'+a.value+"open: "+b.yAxis.tickFormat()(c.open)+" close: "+b.yAxis.tickFormat()(c.close)+" high "+b.yAxis.tickFormat()(c.high)+" low: "+b.yAxis.tickFormat()(c.low)+"
"}),b},a.models.legend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?"#000":"#fff":m?void 0:(a.color||(a.color=g(a,b)),a.disabled?a.color:"#fff")}function r(a,b){return m&&"furious"==o&&a.disengaged?"#eee":a.color||g(a,b)}function s(a){return m&&"furious"==o?1:a.disabled?0:1}return p.each(function(b){var g=d-c.left-c.right,p=d3.select(this);a.utils.initSVG(p);var t=p.selectAll("g.nv-legend").data([b]),u=t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),v=t.select("g");t.attr("transform","translate("+c.left+","+c.top+")");var w,x,y=v.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),z=y.enter().append("g").attr("class","nv-series");switch(o){case"furious":x=23;break;case"classic":x=20}if("classic"==o)z.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),w=y.select("circle");else if("furious"==o){z.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),w=y.select(".nv-legend-symbol"),z.append("g").attr("class","nv-check-box").property("innerHTML",' ').attr("transform","translate(-10,-8)scale(0.5)");var A=y.select(".nv-check-box");A.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}z.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var B=y.select("text.nv-legend-text");y.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=y.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=y.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),y.classed("nv-disabled",function(a){return a.userDisabled}),y.exit().remove(),B.attr("fill",q).text(f);var C=0;if(h){var D=[];y.each(function(){var b,c=d3.select(this).select("text");try{if(b=c.node().getComputedTextLength(),0>=b)throw Error()}catch(d){b=a.utils.calcApproxTextWidth(c)}D.push(b+i)});var E=0,F=[];for(C=0;g>C&&Eg&&E>1;){F=[],E--;for(var G=0;G(F[G%E]||0)&&(F[G%E]=D[G]);C=F.reduce(function(a,b){return a+b})}for(var H=[],I=0,J=0;E>I;I++)H[I]=J,J+=F[I];y.attr("transform",function(a,b){return"translate("+H[b%E]+","+(5+Math.floor(b/E)*x)+")"}),j?v.attr("transform","translate("+(d-c.right-C)+","+c.top+")"):v.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(D.length/E)*x}else{var K,L=5,M=5,N=0;y.attr("transform",function(){var a=d3.select(this).select("text").node().getComputedTextLength()+i;return K=M,dN&&(N=M),K+N>C&&(C=K+N),"translate("+K+","+L+")"}),v.attr("transform","translate("+(d-c.right-N)+","+c.top+")"),e=c.top+c.bottom+L+15}if("furious"==o){w.attr("width",function(a,b){return B[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),u.insert("rect",":first-child").attr("class","nv-legend-bg").attr("fill","#eee").attr("opacity",0);var O=v.select(".nv-legend-bg");O.transition().duration(300).attr("x",-x).attr("width",C+x-12).attr("height",e+10).attr("y",-c.top-10).attr("opacity",m?1:0)}w.style("fill",r).style("fill-opacity",s).style("stroke",r)}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=32,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.line=function(){"use strict";function b(r){return v.reset(),v.models(e),r.each(function(b){i=d3.select(this);var r=a.utils.availableWidth(g,i,f),s=a.utils.availableHeight(h,i,f);a.utils.initSVG(i),c=e.xScale(),d=e.yScale(),t=t||c,u=u||d;var w=i.selectAll("g.nv-wrap.nv-line").data([b]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-line"),y=x.append("defs"),z=x.append("g"),A=w.select("g");z.append("g").attr("class","nv-groups"),z.append("g").attr("class","nv-scatterWrap"),w.attr("transform","translate("+f.left+","+f.top+")"),e.width(r).height(s);var B=w.select(".nv-scatterWrap");B.call(e),y.append("clipPath").attr("id","nv-edge-clip-"+e.id()).append("rect"),w.select("#nv-edge-clip-"+e.id()+" rect").attr("width",r).attr("height",s>0?s:0),A.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":""),B.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":"");var C=w.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});C.enter().append("g").style("stroke-opacity",1e-6).style("stroke-width",function(a){return a.strokeWidth||j}).style("fill-opacity",1e-6),C.exit().remove(),C.attr("class",function(a,b){return(a.classed||"")+" nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return k(a,b)}).style("stroke",function(a,b){return k(a,b)}),C.watchTransition(v,"line: groups").style("stroke-opacity",1).style("fill-opacity",function(a){return a.fillOpacity||.5});var D=C.selectAll("path.nv-area").data(function(a){return o(a)?[a]:[]});D.enter().append("path").attr("class","nv-area").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y0(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))}).y1(function(){return u(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])}),C.exit().selectAll("path.nv-area").remove(),D.watchTransition(v,"line: areaPaths").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y0(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))}).y1(function(){return d(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])});var E=C.selectAll("path.nv-line").data(function(a){return[a.values]});E.enter().append("path").attr("class","nv-line").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))})),E.watchTransition(v,"line: linePaths").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))})),t=c.copy(),u=d.copy()}),v.renderEnd("line immediate"),b}var c,d,e=a.models.scatter(),f={top:0,right:0,bottom:0,left:0},g=960,h=500,i=null,j=1.5,k=a.utils.defaultColor(),l=function(a){return a.x},m=function(a){return a.y},n=function(a,b){return!isNaN(m(a,b))&&null!==m(a,b)},o=function(a){return a.area},p=!1,q="linear",r=250,s=d3.dispatch("elementClick","elementMouseover","elementMouseout","renderEnd");e.pointSize(16).pointDomain([16,256]);var t,u,v=a.utils.renderWatch(s,r);return b.dispatch=s,b.scatter=e,e.dispatch.on("elementClick",function(){s.elementClick.apply(this,arguments)}),e.dispatch.on("elementMouseover",function(){s.elementMouseover.apply(this,arguments)}),e.dispatch.on("elementMouseout",function(){s.elementMouseout.apply(this,arguments)}),b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},defined:{get:function(){return n},set:function(a){n=a}},interpolate:{get:function(){return q},set:function(a){q=a}},clipEdge:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}},duration:{get:function(){return r},set:function(a){r=a,v.reset(r),e.duration(r)}},isArea:{get:function(){return o},set:function(a){o=d3.functor(a)}},x:{get:function(){return l},set:function(a){l=a,e.x(a)}},y:{get:function(){return m},set:function(a){m=a,e.y(a)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.lineChart=function(){"use strict";function b(j){return y.reset(),y.models(e),p&&y.models(f),q&&y.models(g),j.each(function(j){var v=d3.select(this),y=this;a.utils.initSVG(v);var B=a.utils.availableWidth(m,v,k),C=a.utils.availableHeight(n,v,k);if(b.update=function(){0===x?v.call(b):v.transition().duration(x).call(b)},b.container=this,t.setter(A(j),b.update).getter(z(j)).update(),t.disabled=j.map(function(a){return!!a.disabled}),!u){var D;u={};for(D in t)u[D]=t[D]instanceof Array?t[D].slice(0):t[D]
+}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,v),b;v.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var E=v.selectAll("g.nv-wrap.nv-lineChart").data([j]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-lineChart").append("g"),G=E.select("g");F.append("rect").style("opacity",0),F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-linesWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),G.select("rect").attr("width",B).attr("height",C>0?C:0),o&&(h.width(B),G.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),C=a.utils.availableHeight(n,v,k)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-k.top+")")),E.attr("transform","translate("+k.left+","+k.top+")"),r&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),s&&(i.width(B).height(C).margin({left:k.left,top:k.top}).svgContainer(v).xScale(c),E.select(".nv-interactive").call(i)),e.width(B).height(C).color(j.map(function(a,b){return a.color||l(a,b)}).filter(function(a,b){return!j[b].disabled}));var H=G.select(".nv-linesWrap").datum(j.filter(function(a){return!a.disabled}));H.call(e),p&&(f.scale(c)._ticks(a.utils.calcTicksX(B/100,j)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),G.select(".nv-x.nv-axis").call(f)),q&&(g.scale(d)._ticks(a.utils.calcTicksY(C/36,j)).tickSize(-B,0),G.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)t[c]=a[c];w.stateChange(t),b.update()}),i.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,h,m,n=[];if(j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,g){h=a.interactiveBisect(f.values,c.pointXValue,b.x());var i=f.values[h],j=b.y()(i,h);null!=j&&e.highlightPoint(g,h,!0),void 0!==i&&(void 0===d&&(d=i),void 0===m&&(m=b.xScale()(b.x()(i,h))),n.push({key:f.key,value:j,color:l(f,f.seriesIndex)}))}),n.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(n.map(function(a){return a.value}),o,q);null!==r&&(n[r].highlight=!0)}var s=f.tickFormat()(b.x()(d,h));i.tooltip.position({left:c.mouseX+k.left,top:c.mouseY+k.top}).chartContainer(y.parentNode).valueFormatter(function(a){return null==a?"N/A":g.tickFormat()(a)}).data({value:s,index:h,series:n})(),i.renderGuideLine(m)}),i.dispatch.on("elementClick",function(c){var d,f=[];j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(e){var g=a.interactiveBisect(e.values,c.pointXValue,b.x()),h=e.values[g];if("undefined"!=typeof h){"undefined"==typeof d&&(d=b.xScale()(b.x()(h,g)));var i=b.yScale()(b.y()(h,g));f.push({point:h,pointIndex:g,pos:[d,i],seriesIndex:e.seriesIndex,series:e})}}),e.dispatch.elementClick(f)}),i.dispatch.on("elementMouseout",function(){e.clearHighlights()}),w.on("changeState",function(a){"undefined"!=typeof a.disabled&&j.length===a.disabled.length&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),t.disabled=a.disabled),b.update()})}),y.renderEnd("lineChart immediate"),b}var c,d,e=a.models.line(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.interactiveGuideline(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=a.utils.defaultColor(),m=null,n=null,o=!0,p=!0,q=!0,r=!1,s=!1,t=a.utils.state(),u=null,v=null,w=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),x=250;f.orient("bottom").tickPadding(7),g.orient(r?"right":"left"),j.valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)});var y=a.utils.renderWatch(w,x),z=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},A=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){j.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),b.dispatch=w,b.lines=e,b.legend=h,b.xAxis=f,b.yAxis=g,b.interactiveLayer=i,b.tooltip=j,b.dispatch=w,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return p},set:function(a){p=a}},showYAxis:{get:function(){return q},set:function(a){q=a}},defaultState:{get:function(){return u},set:function(a){u=a}},noData:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x),e.duration(x),f.duration(x),g.duration(x)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),h.color(l),e.color(l)}},rightAlignYAxis:{get:function(){return r},set:function(a){r=a,g.orient(r?"right":"left")}},useInteractiveGuideline:{get:function(){return s},set:function(a){s=a,s&&(e.interactive(!1),e.useVoronoi(!1))}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.linePlusBarChart=function(){"use strict";function b(v){return v.each(function(v){function J(a){var b=+("e"==a),c=b?1:-1,d=X/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function S(){u.empty()||u.extent(I),kb.data([u.empty()?e.domain():I]).each(function(a){var b=e(a[0])-e.range()[0],c=e.range()[1]-e(a[1]);d3.select(this).select(".left").attr("width",0>b?0:b),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>c?0:c)})}function T(){I=u.empty()?null:u.extent(),c=u.empty()?e.domain():u.extent(),K.brush({extent:c,brush:u}),S(),l.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),j.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var b=db.select(".nv-focus .nv-barsWrap").datum(Z.length?Z.map(function(a){return{key:a.key,values:a.values.filter(function(a,b){return l.x()(a,b)>=c[0]&&l.x()(a,b)<=c[1]})}}):[{values:[]}]),h=db.select(".nv-focus .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$.map(function(a){return{area:a.area,fillOpacity:a.fillOpacity,key:a.key,values:a.values.filter(function(a,b){return j.x()(a,b)>=c[0]&&j.x()(a,b)<=c[1]})}}));d=Z.length?l.xScale():j.xScale(),n.scale(d)._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-W,0),n.domain([Math.ceil(c[0]),Math.floor(c[1])]),db.select(".nv-x.nv-axis").transition().duration(L).call(n),b.transition().duration(L).call(l),h.transition().duration(L).call(j),db.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),p.scale(f)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(-V,0),q.scale(g)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(Z.length?0:-V,0),db.select(".nv-focus .nv-y1.nv-axis").style("opacity",Z.length?1:0),db.select(".nv-focus .nv-y2.nv-axis").style("opacity",$.length&&!$[0].disabled?1:0).attr("transform","translate("+d.range()[1]+",0)"),db.select(".nv-focus .nv-y1.nv-axis").transition().duration(L).call(p),db.select(".nv-focus .nv-y2.nv-axis").transition().duration(L).call(q)}var U=d3.select(this);a.utils.initSVG(U);var V=a.utils.availableWidth(y,U,w),W=a.utils.availableHeight(z,U,w)-(E?H:0),X=H-x.top-x.bottom;if(b.update=function(){U.transition().duration(L).call(b)},b.container=this,M.setter(R(v),b.update).getter(Q(v)).update(),M.disabled=v.map(function(a){return!!a.disabled}),!N){var Y;N={};for(Y in M)N[Y]=M[Y]instanceof Array?M[Y].slice(0):M[Y]}if(!(v&&v.length&&v.filter(function(a){return a.values.length}).length))return a.utils.noData(b,U),b;U.selectAll(".nv-noData").remove();var Z=v.filter(function(a){return!a.disabled&&a.bar}),$=v.filter(function(a){return!a.bar});d=l.xScale(),e=o.scale(),f=l.yScale(),g=j.yScale(),h=m.yScale(),i=k.yScale();var _=v.filter(function(a){return!a.disabled&&a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})}),ab=v.filter(function(a){return!a.disabled&&!a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})});d.range([0,V]),e.domain(d3.extent(d3.merge(_.concat(ab)),function(a){return a.x})).range([0,V]);var bb=U.selectAll("g.nv-wrap.nv-linePlusBar").data([v]),cb=bb.enter().append("g").attr("class","nvd3 nv-wrap nv-linePlusBar").append("g"),db=bb.select("g");cb.append("g").attr("class","nv-legendWrap");var eb=cb.append("g").attr("class","nv-focus");eb.append("g").attr("class","nv-x nv-axis"),eb.append("g").attr("class","nv-y1 nv-axis"),eb.append("g").attr("class","nv-y2 nv-axis"),eb.append("g").attr("class","nv-barsWrap"),eb.append("g").attr("class","nv-linesWrap");var fb=cb.append("g").attr("class","nv-context");if(fb.append("g").attr("class","nv-x nv-axis"),fb.append("g").attr("class","nv-y1 nv-axis"),fb.append("g").attr("class","nv-y2 nv-axis"),fb.append("g").attr("class","nv-barsWrap"),fb.append("g").attr("class","nv-linesWrap"),fb.append("g").attr("class","nv-brushBackground"),fb.append("g").attr("class","nv-x nv-brush"),D){var gb=t.align()?V/2:V,hb=t.align()?gb:0;t.width(gb),db.select(".nv-legendWrap").datum(v.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(a.bar?O:P),a})).call(t),w.top!=t.height()&&(w.top=t.height(),W=a.utils.availableHeight(z,U,w)-H),db.select(".nv-legendWrap").attr("transform","translate("+hb+","+-w.top+")")}bb.attr("transform","translate("+w.left+","+w.top+")"),db.select(".nv-context").style("display",E?"initial":"none"),m.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),k.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var ib=db.select(".nv-context .nv-barsWrap").datum(Z.length?Z:[{values:[]}]),jb=db.select(".nv-context .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$);db.select(".nv-context").attr("transform","translate(0,"+(W+w.bottom+x.top)+")"),ib.transition().call(m),jb.transition().call(k),G&&(o._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-X,0),db.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+h.range()[0]+")"),db.select(".nv-context .nv-x.nv-axis").transition().call(o)),F&&(r.scale(h)._ticks(X/36).tickSize(-V,0),s.scale(i)._ticks(X/36).tickSize(Z.length?0:-V,0),db.select(".nv-context .nv-y3.nv-axis").style("opacity",Z.length?1:0).attr("transform","translate(0,"+e.range()[0]+")"),db.select(".nv-context .nv-y2.nv-axis").style("opacity",$.length?1:0).attr("transform","translate("+e.range()[1]+",0)"),db.select(".nv-context .nv-y1.nv-axis").transition().call(r),db.select(".nv-context .nv-y2.nv-axis").transition().call(s)),u.x(e).on("brush",T),I&&u.extent(I);var kb=db.select(".nv-brushBackground").selectAll("g").data([I||u.extent()]),lb=kb.enter().append("g");lb.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",X),lb.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",X);var mb=db.select(".nv-x.nv-brush").call(u);mb.selectAll("rect").attr("height",X),mb.selectAll(".resize").append("path").attr("d",J),t.dispatch.on("stateChange",function(a){for(var c in a)M[c]=a[c];K.stateChange(M),b.update()}),K.on("changeState",function(a){"undefined"!=typeof a.disabled&&(v.forEach(function(b,c){b.disabled=a.disabled[c]}),M.disabled=a.disabled),b.update()}),T()}),b}var c,d,e,f,g,h,i,j=a.models.line(),k=a.models.line(),l=a.models.historicalBar(),m=a.models.historicalBar(),n=a.models.axis(),o=a.models.axis(),p=a.models.axis(),q=a.models.axis(),r=a.models.axis(),s=a.models.axis(),t=a.models.legend(),u=d3.svg.brush(),v=a.models.tooltip(),w={top:30,right:30,bottom:30,left:60},x={top:0,right:30,bottom:20,left:60},y=null,z=null,A=function(a){return a.x},B=function(a){return a.y},C=a.utils.defaultColor(),D=!0,E=!0,F=!1,G=!0,H=50,I=null,J=null,K=d3.dispatch("brush","stateChange","changeState"),L=0,M=a.utils.state(),N=null,O=" (left axis)",P=" (right axis)";j.clipEdge(!0),k.interactive(!1),n.orient("bottom").tickPadding(5),p.orient("left"),q.orient("right"),o.orient("bottom").tickPadding(5),r.orient("left"),s.orient("right"),v.headerEnabled(!0).headerFormatter(function(a,b){return n.tickFormat()(a,b)});var Q=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},R=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return j.dispatch.on("elementMouseover.tooltip",function(a){v.duration(100).valueFormatter(function(a,b){return q.tickFormat()(a,b)}).data(a).position(a.pos).hidden(!1)}),j.dispatch.on("elementMouseout.tooltip",function(){v.hidden(!0)}),l.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={value:b.y()(a.data),color:a.color},v.duration(0).valueFormatter(function(a,b){return p.tickFormat()(a,b)}).data(a).hidden(!1)}),l.dispatch.on("elementMouseout.tooltip",function(){v.hidden(!0)}),l.dispatch.on("elementMousemove.tooltip",function(){v.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=K,b.legend=t,b.lines=j,b.lines2=k,b.bars=l,b.bars2=m,b.xAxis=n,b.x2Axis=o,b.y1Axis=p,b.y2Axis=q,b.y3Axis=r,b.y4Axis=s,b.tooltip=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return y},set:function(a){y=a}},height:{get:function(){return z},set:function(a){z=a}},showLegend:{get:function(){return D},set:function(a){D=a}},brushExtent:{get:function(){return I},set:function(a){I=a}},noData:{get:function(){return J},set:function(a){J=a}},focusEnable:{get:function(){return E},set:function(a){E=a}},focusHeight:{get:function(){return H},set:function(a){H=a}},focusShowAxisX:{get:function(){return G},set:function(a){G=a}},focusShowAxisY:{get:function(){return F},set:function(a){F=a}},legendLeftAxisHint:{get:function(){return O},set:function(a){O=a}},legendRightAxisHint:{get:function(){return P},set:function(a){P=a}},tooltips:{get:function(){return v.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),v.enabled(!!b)}},tooltipContent:{get:function(){return v.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),v.contentGenerator(b)}},margin:{get:function(){return w},set:function(a){w.top=void 0!==a.top?a.top:w.top,w.right=void 0!==a.right?a.right:w.right,w.bottom=void 0!==a.bottom?a.bottom:w.bottom,w.left=void 0!==a.left?a.left:w.left}},duration:{get:function(){return L},set:function(a){L=a}},color:{get:function(){return C},set:function(b){C=a.utils.getColor(b),t.color(C)}},x:{get:function(){return A},set:function(a){A=a,j.x(a),k.x(a),l.x(a),m.x(a)}},y:{get:function(){return B},set:function(a){B=a,j.y(a),k.y(a),l.y(a),m.y(a)}}}),a.utils.inheritOptions(b,j),a.utils.initOptions(b),b},a.models.lineWithFocusChart=function(){"use strict";function b(o){return o.each(function(o){function z(a){var b=+("e"==a),c=b?1:-1,d=M/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function G(){n.empty()||n.extent(y),U.data([n.empty()?e.domain():y]).each(function(a){var b=e(a[0])-c.range()[0],d=K-e(a[1]);d3.select(this).select(".left").attr("width",0>b?0:b),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>d?0:d)})}function H(){y=n.empty()?null:n.extent();var a=n.empty()?e.domain():n.extent();if(!(Math.abs(a[0]-a[1])<=1)){A.brush({extent:a,brush:n}),G();var b=Q.select(".nv-focus .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}).map(function(b){return{key:b.key,area:b.area,values:b.values.filter(function(b,c){return g.x()(b,c)>=a[0]&&g.x()(b,c)<=a[1]})}}));b.transition().duration(B).call(g),Q.select(".nv-focus .nv-x.nv-axis").transition().duration(B).call(i),Q.select(".nv-focus .nv-y.nv-axis").transition().duration(B).call(j)}}var I=d3.select(this),J=this;a.utils.initSVG(I);var K=a.utils.availableWidth(t,I,q),L=a.utils.availableHeight(u,I,q)-v,M=v-r.top-r.bottom;if(b.update=function(){I.transition().duration(B).call(b)},b.container=this,C.setter(F(o),b.update).getter(E(o)).update(),C.disabled=o.map(function(a){return!!a.disabled}),!D){var N;D={};for(N in C)D[N]=C[N]instanceof Array?C[N].slice(0):C[N]}if(!(o&&o.length&&o.filter(function(a){return a.values.length}).length))return a.utils.noData(b,I),b;I.selectAll(".nv-noData").remove(),c=g.xScale(),d=g.yScale(),e=h.xScale(),f=h.yScale();var O=I.selectAll("g.nv-wrap.nv-lineWithFocusChart").data([o]),P=O.enter().append("g").attr("class","nvd3 nv-wrap nv-lineWithFocusChart").append("g"),Q=O.select("g");P.append("g").attr("class","nv-legendWrap");var R=P.append("g").attr("class","nv-focus");R.append("g").attr("class","nv-x nv-axis"),R.append("g").attr("class","nv-y nv-axis"),R.append("g").attr("class","nv-linesWrap"),R.append("g").attr("class","nv-interactive");var S=P.append("g").attr("class","nv-context");S.append("g").attr("class","nv-x nv-axis"),S.append("g").attr("class","nv-y nv-axis"),S.append("g").attr("class","nv-linesWrap"),S.append("g").attr("class","nv-brushBackground"),S.append("g").attr("class","nv-x nv-brush"),x&&(m.width(K),Q.select(".nv-legendWrap").datum(o).call(m),q.top!=m.height()&&(q.top=m.height(),L=a.utils.availableHeight(u,I,q)-v),Q.select(".nv-legendWrap").attr("transform","translate(0,"+-q.top+")")),O.attr("transform","translate("+q.left+","+q.top+")"),w&&(p.width(K).height(L).margin({left:q.left,top:q.top}).svgContainer(I).xScale(c),O.select(".nv-interactive").call(p)),g.width(K).height(L).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),h.defined(g.defined()).width(K).height(M).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),Q.select(".nv-context").attr("transform","translate(0,"+(L+q.bottom+r.top)+")");var T=Q.select(".nv-context .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}));d3.transition(T).call(h),i.scale(c)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-L,0),j.scale(d)._ticks(a.utils.calcTicksY(L/36,o)).tickSize(-K,0),Q.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+L+")"),n.x(e).on("brush",function(){H()}),y&&n.extent(y);var U=Q.select(".nv-brushBackground").selectAll("g").data([y||n.extent()]),V=U.enter().append("g");V.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",M),V.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",M);var W=Q.select(".nv-x.nv-brush").call(n);W.selectAll("rect").attr("height",M),W.selectAll(".resize").append("path").attr("d",z),H(),k.scale(e)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-M,0),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),d3.transition(Q.select(".nv-context .nv-x.nv-axis")).call(k),l.scale(f)._ticks(a.utils.calcTicksY(M/36,o)).tickSize(-K,0),d3.transition(Q.select(".nv-context .nv-y.nv-axis")).call(l),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),m.dispatch.on("stateChange",function(a){for(var c in a)C[c]=a[c];A.stateChange(C),b.update()}),p.dispatch.on("elementMousemove",function(c){g.clearHighlights();var d,f,h,k=[];if(o.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(i,j){var l=n.empty()?e.domain():n.extent(),m=i.values.filter(function(a,b){return g.x()(a,b)>=l[0]&&g.x()(a,b)<=l[1]});f=a.interactiveBisect(m,c.pointXValue,g.x());var o=m[f],p=b.y()(o,f);null!=p&&g.highlightPoint(j,f,!0),void 0!==o&&(void 0===d&&(d=o),void 0===h&&(h=b.xScale()(b.x()(o,f))),k.push({key:i.key,value:b.y()(o,f),color:s(i,i.seriesIndex)}))}),k.length>2){var l=b.yScale().invert(c.mouseY),m=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),r=.03*m,t=a.nearestValueIndex(k.map(function(a){return a.value}),l,r);null!==t&&(k[t].highlight=!0)}var u=i.tickFormat()(b.x()(d,f));p.tooltip.position({left:c.mouseX+q.left,top:c.mouseY+q.top}).chartContainer(J.parentNode).valueFormatter(function(a){return null==a?"N/A":j.tickFormat()(a)}).data({value:u,index:f,series:k})(),p.renderGuideLine(h)}),p.dispatch.on("elementMouseout",function(){g.clearHighlights()}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&o.forEach(function(b,c){b.disabled=a.disabled[c]}),b.update()})}),b}var c,d,e,f,g=a.models.line(),h=a.models.line(),i=a.models.axis(),j=a.models.axis(),k=a.models.axis(),l=a.models.axis(),m=a.models.legend(),n=d3.svg.brush(),o=a.models.tooltip(),p=a.interactiveGuideline(),q={top:30,right:30,bottom:30,left:60},r={top:0,right:30,bottom:20,left:60},s=a.utils.defaultColor(),t=null,u=null,v=50,w=!1,x=!0,y=null,z=null,A=d3.dispatch("brush","stateChange","changeState"),B=250,C=a.utils.state(),D=null;g.clipEdge(!0).duration(0),h.interactive(!1),i.orient("bottom").tickPadding(5),j.orient("left"),k.orient("bottom").tickPadding(5),l.orient("left"),o.valueFormatter(function(a,b){return j.tickFormat()(a,b)}).headerFormatter(function(a,b){return i.tickFormat()(a,b)});var E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return g.dispatch.on("elementMouseover.tooltip",function(a){o.data(a).position(a.pos).hidden(!1)}),g.dispatch.on("elementMouseout.tooltip",function(){o.hidden(!0)}),b.dispatch=A,b.legend=m,b.lines=g,b.lines2=h,b.xAxis=i,b.yAxis=j,b.x2Axis=k,b.y2Axis=l,b.interactiveLayer=p,b.tooltip=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return t},set:function(a){t=a}},height:{get:function(){return u},set:function(a){u=a}},focusHeight:{get:function(){return v},set:function(a){v=a}},showLegend:{get:function(){return x},set:function(a){x=a}},brushExtent:{get:function(){return y},set:function(a){y=a}},defaultState:{get:function(){return D},set:function(a){D=a}},noData:{get:function(){return z},set:function(a){z=a}},tooltips:{get:function(){return o.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),o.enabled(!!b)}},tooltipContent:{get:function(){return o.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),o.contentGenerator(b)}},margin:{get:function(){return q},set:function(a){q.top=void 0!==a.top?a.top:q.top,q.right=void 0!==a.right?a.right:q.right,q.bottom=void 0!==a.bottom?a.bottom:q.bottom,q.left=void 0!==a.left?a.left:q.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b),m.color(s)}},interpolate:{get:function(){return g.interpolate()},set:function(a){g.interpolate(a),h.interpolate(a)}},xTickFormat:{get:function(){return i.tickFormat()},set:function(a){i.tickFormat(a),k.tickFormat(a)}},yTickFormat:{get:function(){return j.tickFormat()},set:function(a){j.tickFormat(a),l.tickFormat(a)}},duration:{get:function(){return B},set:function(a){B=a,j.duration(B),l.duration(B),i.duration(B),k.duration(B)}},x:{get:function(){return g.x()},set:function(a){g.x(a),h.x(a)}},y:{get:function(){return g.y()},set:function(a){g.y(a),h.y(a)}},useInteractiveGuideline:{get:function(){return w},set:function(a){w=a,w&&(g.interactive(!1),g.useVoronoi(!1))}}}),a.utils.inheritOptions(b,g),a.utils.initOptions(b),b},a.models.multiBar=function(){"use strict";function b(E){return C.reset(),E.each(function(b){var E=k-j.left-j.right,F=l-j.top-j.bottom;p=d3.select(this),a.utils.initSVG(p);var G=0;if(x&&b.length&&(x=[{values:b[0].values.map(function(a){return{x:a.x,y:0,series:a.series,size:.01}})}]),u){var H=d3.layout.stack().offset(v).values(function(a){return a.values}).y(r)(!b.length&&x?x:b);H.forEach(function(a,c){a.nonStackable?(b[c].nonStackableSeries=G++,H[c]=b[c]):c>0&&H[c-1].nonStackable&&H[c].values.map(function(a,b){a.y0-=H[c-1].values[b].y,a.y1=a.y0+a.y})}),b=H}b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),u&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a,f){if(!b[f].nonStackable){var g=a.values[c];g.size=Math.abs(g.y),g.y<0?(g.y1=e,e-=g.size):(g.y1=g.size+d,d+=g.size)}})});var I=d&&e?[]:b.map(function(a,b){return a.values.map(function(a,c){return{x:q(a,c),y:r(a,c),y0:a.y0,y1:a.y1,idx:b}})});m.domain(d||d3.merge(I).map(function(a){return a.x})).rangeBands(f||[0,E],A),n.domain(e||d3.extent(d3.merge(I).map(function(a){var c=a.y;return u&&!b[a.idx].nonStackable&&(c=a.y>0?a.y1:a.y1+a.y),c}).concat(s))).range(g||[F,0]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]:[-1,1]),n.domain()[0]===n.domain()[1]&&n.domain(n.domain()[0]?[n.domain()[0]+.01*n.domain()[0],n.domain()[1]-.01*n.domain()[1]]:[-1,1]),h=h||m,i=i||n;var J=p.selectAll("g.nv-wrap.nv-multibar").data([b]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-multibar"),L=K.append("defs"),M=K.append("g"),N=J.select("g");M.append("g").attr("class","nv-groups"),J.attr("transform","translate("+j.left+","+j.top+")"),L.append("clipPath").attr("id","nv-edge-clip-"+o).append("rect"),J.select("#nv-edge-clip-"+o+" rect").attr("width",E).attr("height",F),N.attr("clip-path",t?"url(#nv-edge-clip-"+o+")":"");var O=J.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});O.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);var P=C.transition(O.exit().selectAll("rect.nv-bar"),"multibarExit",Math.min(100,z)).attr("y",function(a){var c=i(0)||0;return u&&b[a.series]&&!b[a.series].nonStackable&&(c=i(a.y0)),c}).attr("height",0).remove();P.delay&&P.delay(function(a,b){var c=b*(z/(D+1))-b;return c}),O.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return w(a,b)}).style("stroke",function(a,b){return w(a,b)}),O.style("stroke-opacity",1).style("fill-opacity",.75);var Q=O.selectAll("rect.nv-bar").data(function(a){return x&&!b.length?x.values:a.values});Q.exit().remove();Q.enter().append("rect").attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("x",function(a,c,d){return u&&!b[d].nonStackable?0:d*m.rangeBand()/b.length}).attr("y",function(a,c,d){return i(u&&!b[d].nonStackable?a.y0:0)||0}).attr("height",0).attr("width",function(a,c,d){return m.rangeBand()/(u&&!b[d].nonStackable?1:b.length)}).attr("transform",function(a,b){return"translate("+m(q(a,b))+",0)"});Q.style("fill",function(a,b,c){return w(a,c,b)}).style("stroke",function(a,b,c){return w(a,c,b)}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),B.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),B.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){B.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){B.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){B.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),Q.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("transform",function(a,b){return"translate("+m(q(a,b))+",0)"}),y&&(c||(c=b.map(function(){return!0})),Q.style("fill",function(a,b,d){return d3.rgb(y(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(y(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}));var R=Q.watchTransition(C,"multibar",Math.min(250,z)).delay(function(a,c){return c*z/b[0].values.length});u?R.attr("y",function(a,c,d){var e=0;return e=b[d].nonStackable?r(a,c)<0?n(0):n(0)-n(r(a,c))<-1?n(0)-1:n(r(a,c))||0:n(a.y1)}).attr("height",function(a,c,d){return b[d].nonStackable?Math.max(Math.abs(n(r(a,c))-n(0)),1)||0:Math.max(Math.abs(n(a.y+a.y0)-n(a.y0)),1)}).attr("x",function(a,c,d){var e=0;return b[d].nonStackable&&(e=a.series*m.rangeBand()/b.length,b.length!==G&&(e=b[d].nonStackableSeries*m.rangeBand()/(2*G))),e}).attr("width",function(a,c,d){if(b[d].nonStackable){var e=m.rangeBand()/G;return b.length!==G&&(e=m.rangeBand()/(2*G)),e}return m.rangeBand()}):R.attr("x",function(a){return a.series*m.rangeBand()/b.length}).attr("width",m.rangeBand()/b.length).attr("y",function(a,b){return r(a,b)<0?n(0):n(0)-n(r(a,b))<1?n(0)-1:n(r(a,b))||0}).attr("height",function(a,b){return Math.max(Math.abs(n(r(a,b))-n(0)),1)||0}),h=m.copy(),i=n.copy(),b[0]&&b[0].values&&(D=b[0].values.length)}),C.renderEnd("multibar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=d3.scale.ordinal(),n=d3.scale.linear(),o=Math.floor(1e4*Math.random()),p=null,q=function(a){return a.x},r=function(a){return a.y},s=[0],t=!0,u=!1,v="zero",w=a.utils.defaultColor(),x=!1,y=null,z=500,A=.1,B=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),C=a.utils.renderWatch(B,z),D=0;return b.dispatch=B,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return s},set:function(a){s=a}},stacked:{get:function(){return u},set:function(a){u=a}},stackOffset:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return t},set:function(a){t=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return o},set:function(a){o=a}},hideable:{get:function(){return x},set:function(a){x=a}},groupSpacing:{get:function(){return A},set:function(a){A=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return z},set:function(a){z=a,C.reset(z)}},color:{get:function(){return w},set:function(b){w=a.utils.getColor(b)}},barColor:{get:function(){return y},set:function(b){y=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarChart=function(){"use strict";function b(j){return D.reset(),D.models(e),r&&D.models(f),s&&D.models(g),j.each(function(j){var z=d3.select(this);a.utils.initSVG(z);var D=a.utils.availableWidth(l,z,k),H=a.utils.availableHeight(m,z,k);if(b.update=function(){0===C?z.call(b):z.transition().duration(C).call(b)},b.container=this,x.setter(G(j),b.update).getter(F(j)).update(),x.disabled=j.map(function(a){return!!a.disabled}),!y){var I;y={};for(I in x)y[I]=x[I]instanceof Array?x[I].slice(0):x[I]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,z),b;z.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();
+var J=z.selectAll("g.nv-wrap.nv-multiBarWithLegend").data([j]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarWithLegend").append("g"),L=J.select("g");if(K.append("g").attr("class","nv-x nv-axis"),K.append("g").attr("class","nv-y nv-axis"),K.append("g").attr("class","nv-barsWrap"),K.append("g").attr("class","nv-legendWrap"),K.append("g").attr("class","nv-controlsWrap"),q&&(h.width(D-B()),L.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),H=a.utils.availableHeight(m,z,k)),L.select(".nv-legendWrap").attr("transform","translate("+B()+","+-k.top+")")),o){var M=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(B()).color(["#444","#444","#444"]),L.select(".nv-controlsWrap").datum(M).attr("transform","translate(0,"+-k.top+")").call(i)}J.attr("transform","translate("+k.left+","+k.top+")"),t&&L.select(".nv-y.nv-axis").attr("transform","translate("+D+",0)"),e.disabled(j.map(function(a){return a.disabled})).width(D).height(H).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var N=L.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(N.call(e),r){f.scale(c)._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-H,0),L.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),L.select(".nv-x.nv-axis").call(f);var O=L.select(".nv-x.nv-axis > g").selectAll("g");if(O.selectAll("line, text").style("opacity",1),v){var P=function(a,b){return"translate("+a+","+b+")"},Q=5,R=17;O.selectAll("text").attr("transform",function(a,b,c){return P(0,c%2==0?Q:R)});var S=d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;L.selectAll(".nv-x.nv-axis .nv-axisMaxMin text").attr("transform",function(a,b){return P(0,0===b||S%2!==0?R:Q)})}u&&O.filter(function(a,b){return b%Math.ceil(j[0].values.length/(D/100))!==0}).selectAll("text, line").style("opacity",0),w&&O.selectAll(".tick text").attr("transform","rotate("+w+" 0,0)").style("text-anchor",w>0?"start":"end"),L.select(".nv-x.nv-axis").selectAll("g.nv-axisMaxMin text").style("opacity",1)}s&&(g.scale(d)._ticks(a.utils.calcTicksY(H/36,j)).tickSize(-D,0),L.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)x[c]=a[c];A.stateChange(x),b.update()}),i.dispatch.on("legendClick",function(a){if(a.disabled){switch(M=M.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":case p.grouped:e.stacked(!1);break;case"Stacked":case p.stacked:e.stacked(!0)}x.stacked=e.stacked(),A.stateChange(x),b.update()}}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),x.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),x.stacked=a.stacked,E=a.stacked),b.update()})}),D.renderEnd("multibarchart immediate"),b}var c,d,e=a.models.multiBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=0,x=a.utils.state(),y=null,z=null,A=d3.dispatch("stateChange","changeState","renderEnd"),B=function(){return o?180:0},C=250;x.stacked=!1,e.stacked(!1),f.orient("bottom").tickPadding(7).showMaxMin(!1).tickFormat(function(a){return a}),g.orient(t?"right":"left").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var D=a.utils.renderWatch(A),E=!1,F=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:E}}},G=function(a){return function(b){void 0!==b.stacked&&(E=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=A,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=x,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return y},set:function(a){y=a}},noData:{get:function(){return z},set:function(a){z=a}},reduceXTicks:{get:function(){return u},set:function(a){u=a}},rotateLabels:{get:function(){return w},set:function(a){w=a}},staggerLabels:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return C},set:function(a){C=a,e.duration(C),f.duration(C),g.duration(C),D.reset(C)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiBarHorizontal=function(){"use strict";function b(m){return E.reset(),m.each(function(b){var m=k-j.left-j.right,C=l-j.top-j.bottom;n=d3.select(this),a.utils.initSVG(n),w&&(b=d3.layout.stack().offset("zero").values(function(a){return a.values}).y(r)(b)),b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),w&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a){var b=a.values[c];b.size=Math.abs(b.y),b.y<0?(b.y1=e-b.size,e-=b.size):(b.y1=d,d+=b.size)})});var F=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:q(a,b),y:r(a,b),y0:a.y0,y1:a.y1}})});o.domain(d||d3.merge(F).map(function(a){return a.x})).rangeBands(f||[0,C],A),p.domain(e||d3.extent(d3.merge(F).map(function(a){return w?a.y>0?a.y1+a.y:a.y1:a.y}).concat(t))),p.range(x&&!w?g||[p.domain()[0]<0?z:0,m-(p.domain()[1]>0?z:0)]:g||[0,m]),h=h||o,i=i||d3.scale.linear().domain(p.domain()).range([p(0),p(0)]);{var G=d3.select(this).selectAll("g.nv-wrap.nv-multibarHorizontal").data([b]),H=G.enter().append("g").attr("class","nvd3 nv-wrap nv-multibarHorizontal"),I=(H.append("defs"),H.append("g"));G.select("g")}I.append("g").attr("class","nv-groups"),G.attr("transform","translate("+j.left+","+j.top+")");var J=G.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});J.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),J.exit().watchTransition(E,"multibarhorizontal: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),J.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return u(a,b)}).style("stroke",function(a,b){return u(a,b)}),J.watchTransition(E,"multibarhorizontal: groups").style("stroke-opacity",1).style("fill-opacity",.75);var K=J.selectAll("g.nv-bar").data(function(a){return a.values});K.exit().remove();var L=K.enter().append("g").attr("transform",function(a,c,d){return"translate("+i(w?a.y0:0)+","+(w?0:d*o.rangeBand()/b.length+o(q(a,c)))+")"});L.append("rect").attr("width",0).attr("height",o.rangeBand()/(w?1:b.length)),K.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),D.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),D.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){D.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){D.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){D.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){D.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),s(b[0],0)&&(L.append("polyline"),K.select("polyline").attr("fill","none").attr("points",function(a,c){var d=s(a,c),e=.8*o.rangeBand()/(2*(w?1:b.length));d=d.length?d:[-Math.abs(d),Math.abs(d)],d=d.map(function(a){return p(a)-p(0)});var f=[[d[0],-e],[d[0],e],[d[0],0],[d[1],0],[d[1],-e],[d[1],e]];return f.map(function(a){return a.join(",")}).join(" ")}).attr("transform",function(a,c){var d=o.rangeBand()/(2*(w?1:b.length));return"translate("+(r(a,c)<0?0:p(r(a,c))-p(0))+", "+d+")"})),L.append("text"),x&&!w?(K.select("text").attr("text-anchor",function(a,b){return r(a,b)<0?"end":"start"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){var c=B(r(a,b)),d=s(a,b);return void 0===d?c:d.length?c+"+"+B(Math.abs(d[1]))+"-"+B(Math.abs(d[0])):c+"±"+B(Math.abs(d))}),K.watchTransition(E,"multibarhorizontal: bars").select("text").attr("x",function(a,b){return r(a,b)<0?-4:p(r(a,b))-p(0)+4})):K.selectAll("text").text(""),y&&!w?(L.append("text").classed("nv-bar-label",!0),K.select("text.nv-bar-label").attr("text-anchor",function(a,b){return r(a,b)<0?"start":"end"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){return q(a,b)}),K.watchTransition(E,"multibarhorizontal: bars").select("text.nv-bar-label").attr("x",function(a,b){return r(a,b)<0?p(0)-p(r(a,b))+4:-4})):K.selectAll("text.nv-bar-label").text(""),K.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}),v&&(c||(c=b.map(function(){return!0})),K.style("fill",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()})),w?K.watchTransition(E,"multibarhorizontal: bars").attr("transform",function(a,b){return"translate("+p(a.y1)+","+o(q(a,b))+")"}).select("rect").attr("width",function(a,b){return Math.abs(p(r(a,b)+a.y0)-p(a.y0))}).attr("height",o.rangeBand()):K.watchTransition(E,"multibarhorizontal: bars").attr("transform",function(a,c){return"translate("+p(r(a,c)<0?r(a,c):0)+","+(a.series*o.rangeBand()/b.length+o(q(a,c)))+")"}).select("rect").attr("height",o.rangeBand()/b.length).attr("width",function(a,b){return Math.max(Math.abs(p(r(a,b))-p(0)),1)}),h=o.copy(),i=p.copy()}),E.renderEnd("multibarHorizontal immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=null,o=d3.scale.ordinal(),p=d3.scale.linear(),q=function(a){return a.x},r=function(a){return a.y},s=function(a){return a.yErr},t=[0],u=a.utils.defaultColor(),v=null,w=!1,x=!1,y=!1,z=60,A=.1,B=d3.format(",.2f"),C=250,D=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),E=a.utils.renderWatch(D,C);return b.dispatch=D,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},yErr:{get:function(){return s},set:function(a){s=a}},xScale:{get:function(){return o},set:function(a){o=a}},yScale:{get:function(){return p},set:function(a){p=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return t},set:function(a){t=a}},stacked:{get:function(){return w},set:function(a){w=a}},showValues:{get:function(){return x},set:function(a){x=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return m},set:function(a){m=a}},valueFormat:{get:function(){return B},set:function(a){B=a}},valuePadding:{get:function(){return z},set:function(a){z=a}},groupSpacing:{get:function(){return A},set:function(a){A=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return C},set:function(a){C=a,E.reset(C)}},color:{get:function(){return u},set:function(b){u=a.utils.getColor(b)}},barColor:{get:function(){return v},set:function(b){v=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarHorizontalChart=function(){"use strict";function b(j){return C.reset(),C.models(e),r&&C.models(f),s&&C.models(g),j.each(function(j){var w=d3.select(this);a.utils.initSVG(w);var C=a.utils.availableWidth(l,w,k),D=a.utils.availableHeight(m,w,k);if(b.update=function(){w.transition().duration(z).call(b)},b.container=this,t=e.stacked(),u.setter(B(j),b.update).getter(A(j)).update(),u.disabled=j.map(function(a){return!!a.disabled}),!v){var E;v={};for(E in u)v[E]=u[E]instanceof Array?u[E].slice(0):u[E]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,w),b;w.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var F=w.selectAll("g.nv-wrap.nv-multiBarHorizontalChart").data([j]),G=F.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarHorizontalChart").append("g"),H=F.select("g");if(G.append("g").attr("class","nv-x nv-axis"),G.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),G.append("g").attr("class","nv-barsWrap"),G.append("g").attr("class","nv-legendWrap"),G.append("g").attr("class","nv-controlsWrap"),q&&(h.width(C-y()),H.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),D=a.utils.availableHeight(m,w,k)),H.select(".nv-legendWrap").attr("transform","translate("+y()+","+-k.top+")")),o){var I=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(y()).color(["#444","#444","#444"]),H.select(".nv-controlsWrap").datum(I).attr("transform","translate(0,"+-k.top+")").call(i)}F.attr("transform","translate("+k.left+","+k.top+")"),e.disabled(j.map(function(a){return a.disabled})).width(C).height(D).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var J=H.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(J.transition().call(e),r){f.scale(c)._ticks(a.utils.calcTicksY(D/24,j)).tickSize(-C,0),H.select(".nv-x.nv-axis").call(f);var K=H.select(".nv-x.nv-axis").selectAll("g");K.selectAll("line, text")}s&&(g.scale(d)._ticks(a.utils.calcTicksX(C/100,j)).tickSize(-D,0),H.select(".nv-y.nv-axis").attr("transform","translate(0,"+D+")"),H.select(".nv-y.nv-axis").call(g)),H.select(".nv-zeroLine line").attr("x1",d(0)).attr("x2",d(0)).attr("y1",0).attr("y2",-D),h.dispatch.on("stateChange",function(a){for(var c in a)u[c]=a[c];x.stateChange(u),b.update()}),i.dispatch.on("legendClick",function(a){if(a.disabled){switch(I=I.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":e.stacked(!1);break;case"Stacked":e.stacked(!0)}u.stacked=e.stacked(),x.stateChange(u),t=e.stacked(),b.update()}}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),u.stacked=a.stacked,t=a.stacked),b.update()})}),C.renderEnd("multibar horizontal chart immediate"),b}var c,d,e=a.models.multiBarHorizontal(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend().height(30),i=a.models.legend().height(30),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=a.utils.state(),v=null,w=null,x=d3.dispatch("stateChange","changeState","renderEnd"),y=function(){return o?180:0},z=250;u.stacked=!1,e.stacked(t),f.orient("left").tickPadding(5).showMaxMin(!1).tickFormat(function(a){return a}),g.orient("bottom").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var A=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:t}}},B=function(a){return function(b){void 0!==b.stacked&&(t=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},C=a.utils.renderWatch(x,z);return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=x,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=u,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return z},set:function(a){z=a,C.reset(z),e.duration(z),f.duration(z),g.duration(z)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiChart=function(){"use strict";function b(j){return j.each(function(j){function k(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.value=a.point.x,a.series={value:a.point.y,color:a.point.color},B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function l(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.point.x=v.x()(a.point),a.point.y=v.y()(a.point),B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function n(a){var b=2===j[a.data.series].yAxis?z:y;a.value=t.x()(a.data),a.series={value:t.y()(a.data),color:a.color},B.duration(0).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).hidden(!1)}var C=d3.select(this);a.utils.initSVG(C),b.update=function(){C.transition().call(b)},b.container=this;var D=a.utils.availableWidth(g,C,e),E=a.utils.availableHeight(h,C,e),F=j.filter(function(a){return"line"==a.type&&1==a.yAxis}),G=j.filter(function(a){return"line"==a.type&&2==a.yAxis}),H=j.filter(function(a){return"bar"==a.type&&1==a.yAxis}),I=j.filter(function(a){return"bar"==a.type&&2==a.yAxis}),J=j.filter(function(a){return"area"==a.type&&1==a.yAxis}),K=j.filter(function(a){return"area"==a.type&&2==a.yAxis});if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,C),b;C.selectAll(".nv-noData").remove();var L=j.filter(function(a){return!a.disabled&&1==a.yAxis}).map(function(a){return a.values.map(function(a){return{x:a.x,y:a.y}})}),M=j.filter(function(a){return!a.disabled&&2==a.yAxis}).map(function(a){return a.values.map(function(a){return{x:a.x,y:a.y}})});o.domain(d3.extent(d3.merge(L.concat(M)),function(a){return a.x})).range([0,D]);var N=C.selectAll("g.wrap.multiChart").data([j]),O=N.enter().append("g").attr("class","wrap nvd3 multiChart").append("g");O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y1 nv-axis"),O.append("g").attr("class","nv-y2 nv-axis"),O.append("g").attr("class","lines1Wrap"),O.append("g").attr("class","lines2Wrap"),O.append("g").attr("class","bars1Wrap"),O.append("g").attr("class","bars2Wrap"),O.append("g").attr("class","stack1Wrap"),O.append("g").attr("class","stack2Wrap"),O.append("g").attr("class","legendWrap");var P=N.select("g"),Q=j.map(function(a,b){return j[b].color||f(a,b)});if(i){var R=A.align()?D/2:D,S=A.align()?R:0;A.width(R),A.color(Q),P.select(".legendWrap").datum(j.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(1==a.yAxis?"":" (right axis)"),a})).call(A),e.top!=A.height()&&(e.top=A.height(),E=a.utils.availableHeight(h,C,e)),P.select(".legendWrap").attr("transform","translate("+S+","+-e.top+")")}r.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"line"==j[b].type})),s.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"line"==j[b].type})),t.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"bar"==j[b].type})),u.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"bar"==j[b].type})),v.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"area"==j[b].type})),w.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"area"==j[b].type})),P.attr("transform","translate("+e.left+","+e.top+")");var T=P.select(".lines1Wrap").datum(F.filter(function(a){return!a.disabled})),U=P.select(".bars1Wrap").datum(H.filter(function(a){return!a.disabled})),V=P.select(".stack1Wrap").datum(J.filter(function(a){return!a.disabled})),W=P.select(".lines2Wrap").datum(G.filter(function(a){return!a.disabled})),X=P.select(".bars2Wrap").datum(I.filter(function(a){return!a.disabled})),Y=P.select(".stack2Wrap").datum(K.filter(function(a){return!a.disabled})),Z=J.length?J.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[],$=K.length?K.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[];p.domain(c||d3.extent(d3.merge(L).concat(Z),function(a){return a.y})).range([0,E]),q.domain(d||d3.extent(d3.merge(M).concat($),function(a){return a.y})).range([0,E]),r.yDomain(p.domain()),t.yDomain(p.domain()),v.yDomain(p.domain()),s.yDomain(q.domain()),u.yDomain(q.domain()),w.yDomain(q.domain()),J.length&&d3.transition(V).call(v),K.length&&d3.transition(Y).call(w),H.length&&d3.transition(U).call(t),I.length&&d3.transition(X).call(u),F.length&&d3.transition(T).call(r),G.length&&d3.transition(W).call(s),x._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-E,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+E+")"),d3.transition(P.select(".nv-x.nv-axis")).call(x),y._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y1.nv-axis")).call(y),z._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y2.nv-axis")).call(z),P.select(".nv-y1.nv-axis").classed("nv-disabled",L.length?!1:!0).attr("transform","translate("+o.range()[0]+",0)"),P.select(".nv-y2.nv-axis").classed("nv-disabled",M.length?!1:!0).attr("transform","translate("+o.range()[1]+",0)"),A.dispatch.on("stateChange",function(){b.update()}),r.dispatch.on("elementMouseover.tooltip",k),s.dispatch.on("elementMouseover.tooltip",k),r.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),s.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),v.dispatch.on("elementMouseover.tooltip",l),w.dispatch.on("elementMouseover.tooltip",l),v.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),w.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),t.dispatch.on("elementMouseover.tooltip",n),u.dispatch.on("elementMouseover.tooltip",n),t.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),u.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),t.dispatch.on("elementMousemove.tooltip",function(){B.position({top:d3.event.pageY,left:d3.event.pageX})()}),u.dispatch.on("elementMousemove.tooltip",function(){B.position({top:d3.event.pageY,left:d3.event.pageX})()})}),b}var c,d,e={top:30,right:20,bottom:50,left:60},f=a.utils.defaultColor(),g=null,h=null,i=!0,j=null,k=function(a){return a.x},l=function(a){return a.y},m="monotone",n=!0,o=d3.scale.linear(),p=d3.scale.linear(),q=d3.scale.linear(),r=a.models.line().yScale(p),s=a.models.line().yScale(q),t=a.models.multiBar().stacked(!1).yScale(p),u=a.models.multiBar().stacked(!1).yScale(q),v=a.models.stackedArea().yScale(p),w=a.models.stackedArea().yScale(q),x=a.models.axis().scale(o).orient("bottom").tickPadding(5),y=a.models.axis().scale(p).orient("left"),z=a.models.axis().scale(q).orient("right"),A=a.models.legend().height(30),B=a.models.tooltip(),C=d3.dispatch();return b.dispatch=C,b.lines1=r,b.lines2=s,b.bars1=t,b.bars2=u,b.stack1=v,b.stack2=w,b.xAxis=x,b.yAxis1=y,b.yAxis2=z,b.tooltip=B,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},showLegend:{get:function(){return i},set:function(a){i=a}},yDomain1:{get:function(){return c},set:function(a){c=a}},yDomain2:{get:function(){return d},set:function(a){d=a}},noData:{get:function(){return j},set:function(a){j=a}},interpolate:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return B.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),B.enabled(!!b)}},tooltipContent:{get:function(){return B.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),B.contentGenerator(b)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return f},set:function(b){f=a.utils.getColor(b)}},x:{get:function(){return k},set:function(a){k=a,r.x(a),s.x(a),t.x(a),u.x(a),v.x(a),w.x(a)}},y:{get:function(){return l},set:function(a){l=a,r.y(a),s.y(a),v.y(a),w.y(a),t.y(a),u.y(a)}},useVoronoi:{get:function(){return n},set:function(a){n=a,r.useVoronoi(a),s.useVoronoi(a),v.useVoronoi(a),w.useVoronoi(a)}}}),a.utils.initOptions(b),b},a.models.ohlcBar=function(){"use strict";function b(y){return y.each(function(b){k=d3.select(this);var y=a.utils.availableWidth(h,k,g),A=a.utils.availableHeight(i,k,g);a.utils.initSVG(k);var B=y/b[0].values.length*.9;l.domain(c||d3.extent(b[0].values.map(n).concat(t))),l.range(v?e||[.5*y/b[0].values.length,y*(b[0].values.length-.5)/b[0].values.length]:e||[5+B/2,y-B/2-5]),m.domain(d||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(f||[A,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var C=d3.select(this).selectAll("g.nv-wrap.nv-ohlcBar").data([b[0].values]),D=C.enter().append("g").attr("class","nvd3 nv-wrap nv-ohlcBar"),E=D.append("defs"),F=D.append("g"),G=C.select("g");F.append("g").attr("class","nv-ticks"),C.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:j})}),E.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),C.select("#nv-chart-clip-path-"+j+" rect").attr("width",y).attr("height",A),G.attr("clip-path",w?"url(#nv-chart-clip-path-"+j+")":"");var H=C.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});H.exit().remove(),H.enter().append("path").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}).attr("d",function(a,b){return"m0,0l0,"+(m(p(a,b))-m(r(a,b)))+"l"+-B/2+",0l"+B/2+",0l0,"+(m(s(a,b))-m(p(a,b)))+"l0,"+(m(q(a,b))-m(s(a,b)))+"l"+B/2+",0l"+-B/2+",0z"}).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("fill",function(){return x[0]}).attr("stroke",function(){return x[0]}).attr("x",0).attr("y",function(a,b){return m(Math.max(0,o(a,b)))}).attr("height",function(a,b){return Math.abs(m(o(a,b))-m(0))}),H.attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}),d3.transition(H).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("d",function(a,c){var d=y/b[0].values.length*.9;return"m0,0l0,"+(m(p(a,c))-m(r(a,c)))+"l"+-d/2+",0l"+d/2+",0l0,"+(m(s(a,c))-m(p(a,c)))+"l0,"+(m(q(a,c))-m(s(a,c)))+"l"+d/2+",0l"+-d/2+",0z"})}),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,c){b.clearHighlights(),k.select(".nv-ohlcBar .nv-tick-0-"+a).classed("hover",c)},b.clearHighlights=function(){k.select(".nv-ohlcBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!=a.top?a.top:g.top,g.right=void 0!=a.right?a.right:g.right,g.bottom=void 0!=a.bottom?a.bottom:g.bottom,g.left=void 0!=a.left?a.left:g.left
+}},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.parallelCoordinates=function(){"use strict";function b(p){return p.each(function(b){function p(a){return F(h.map(function(b){if(isNaN(a[b])||isNaN(parseFloat(a[b]))){var c=g[b].domain(),d=g[b].range(),e=c[0]-(c[1]-c[0])/9;if(J.indexOf(b)<0){var h=d3.scale.linear().domain([e,c[1]]).range([x-12,d[1]]);g[b].brush.y(h),J.push(b)}return[f(b),g[b](e)]}return J.length>0?(D.style("display","inline"),E.style("display","inline")):(D.style("display","none"),E.style("display","none")),[f(b),g[b](a[b])]}))}function q(){var a=h.filter(function(a){return!g[a].brush.empty()}),b=a.map(function(a){return g[a].brush.extent()});k=[],a.forEach(function(a,c){k[c]={dimension:a,extent:b[c]}}),l=[],M.style("display",function(c){var d=a.every(function(a,d){return isNaN(c[a])&&b[d][0]==g[a].brush.y().domain()[0]?!0:b[d][0]<=c[a]&&c[a]<=b[d][1]});return d&&l.push(c),d?null:"none"}),o.brush({filters:k,active:l})}function r(a){m[a]=this.parentNode.__origin__=f(a),L.attr("visibility","hidden")}function s(a){m[a]=Math.min(w,Math.max(0,this.parentNode.__origin__+=d3.event.x)),M.attr("d",p),h.sort(function(a,b){return u(a)-u(b)}),f.domain(h),N.attr("transform",function(a){return"translate("+u(a)+")"})}function t(a){delete this.parentNode.__origin__,delete m[a],d3.select(this.parentNode).attr("transform","translate("+f(a)+")"),M.attr("d",p),L.attr("d",p).attr("visibility",null)}function u(a){var b=m[a];return null==b?f(a):b}var v=d3.select(this),w=a.utils.availableWidth(d,v,c),x=a.utils.availableHeight(e,v,c);a.utils.initSVG(v),l=b,f.rangePoints([0,w],1).domain(h);var y={};h.forEach(function(a){var c=d3.extent(b,function(b){return+b[a]});return y[a]=!1,void 0===c[0]&&(y[a]=!0,c[0]=0,c[1]=0),c[0]===c[1]&&(c[0]=c[0]-1,c[1]=c[1]+1),g[a]=d3.scale.linear().domain(c).range([.9*(x-12),0]),g[a].brush=d3.svg.brush().y(g[a]).on("brush",q),"name"!=a});var z=v.selectAll("g.nv-wrap.nv-parallelCoordinates").data([b]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-parallelCoordinates"),B=A.append("g"),C=z.select("g");B.append("g").attr("class","nv-parallelCoordinates background"),B.append("g").attr("class","nv-parallelCoordinates foreground"),B.append("g").attr("class","nv-parallelCoordinates missingValuesline"),z.attr("transform","translate("+c.left+","+c.top+")");var D,E,F=d3.svg.line().interpolate("cardinal").tension(n),G=d3.svg.axis().orient("left"),H=d3.behavior.drag().on("dragstart",r).on("drag",s).on("dragend",t),I=f.range()[1]-f.range()[0],J=[],K=[0+I/2,x-12,w-I/2,x-12];D=z.select(".missingValuesline").selectAll("line").data([K]),D.enter().append("line"),D.exit().remove(),D.attr("x1",function(a){return a[0]}).attr("y1",function(a){return a[1]}).attr("x2",function(a){return a[2]}).attr("y2",function(a){return a[3]}),E=z.select(".missingValuesline").selectAll("text").data(["undefined values"]),E.append("text").data(["undefined values"]),E.enter().append("text"),E.exit().remove(),E.attr("y",x).attr("x",w-92-I/2).text(function(a){return a});var L=z.select(".background").selectAll("path").data(b);L.enter().append("path"),L.exit().remove(),L.attr("d",p);var M=z.select(".foreground").selectAll("path").data(b);M.enter().append("path"),M.exit().remove(),M.attr("d",p).attr("stroke",j),M.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),o.elementMouseover({label:a.name,data:a.data,index:b,pos:[d3.mouse(this.parentNode)[0],d3.mouse(this.parentNode)[1]]})}),M.on("mouseout",function(a,b){d3.select(this).classed("hover",!1),o.elementMouseout({label:a.name,data:a.data,index:b})});var N=C.selectAll(".dimension").data(h),O=N.enter().append("g").attr("class","nv-parallelCoordinates dimension");O.append("g").attr("class","nv-parallelCoordinates nv-axis"),O.append("g").attr("class","nv-parallelCoordinates-brush"),O.append("text").attr("class","nv-parallelCoordinates nv-label"),N.attr("transform",function(a){return"translate("+f(a)+",0)"}),N.exit().remove(),N.select(".nv-label").style("cursor","move").attr("dy","-1em").attr("text-anchor","middle").text(String).on("mouseover",function(a){o.elementMouseover({dim:a,pos:[d3.mouse(this.parentNode.parentNode)[0],d3.mouse(this.parentNode.parentNode)[1]]})}).on("mouseout",function(a){o.elementMouseout({dim:a})}).call(H),N.select(".nv-axis").each(function(a,b){d3.select(this).call(G.scale(g[a]).tickFormat(d3.format(i[b])))}),N.select(".nv-parallelCoordinates-brush").each(function(a){d3.select(this).call(g[a].brush)}).selectAll("rect").attr("x",-8).attr("width",16)}),b}var c={top:30,right:0,bottom:10,left:0},d=null,e=null,f=d3.scale.ordinal(),g={},h=[],i=[],j=a.utils.defaultColor(),k=[],l=[],m=[],n=1,o=d3.dispatch("brush","elementMouseover","elementMouseout");return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},dimensionNames:{get:function(){return h},set:function(a){h=a}},dimensionFormats:{get:function(){return i},set:function(a){i=a}},lineTension:{get:function(){return n},set:function(a){n=a}},dimensions:{get:function(){return h},set:function(b){a.deprecated("dimensions","use dimensionNames instead"),h=b}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.pie=function(){"use strict";function b(E){return D.reset(),E.each(function(b){function E(a,b){a.endAngle=isNaN(a.endAngle)?0:a.endAngle,a.startAngle=isNaN(a.startAngle)?0:a.startAngle,p||(a.innerRadius=0);var c=d3.interpolate(this._current,a);return this._current=c(0),function(a){return B[b](c(a))}}var F=d-c.left-c.right,G=e-c.top-c.bottom,H=Math.min(F,G)/2,I=[],J=[];if(i=d3.select(this),0===z.length)for(var K=H-H/5,L=y*H,M=0;Mc)return"";if("function"==typeof n)d=n(a,b,{key:f(a.data),value:g(a.data),percent:k(c)});else switch(n){case"key":d=f(a.data);break;case"value":d=k(g(a.data));break;case"percent":d=d3.format("%")(c)}return d})}}),D.renderEnd("pie immediate"),b}var c={top:0,right:0,bottom:0,left:0},d=500,e=500,f=function(a){return a.x},g=function(a){return a.y},h=Math.floor(1e4*Math.random()),i=null,j=a.utils.defaultColor(),k=d3.format(",.2f"),l=!0,m=!1,n="key",o=.02,p=!1,q=!1,r=!0,s=0,t=!1,u=!1,v=!1,w=!1,x=0,y=.5,z=[],A=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),B=[],C=[],D=a.utils.renderWatch(A);return b.dispatch=A,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{arcsRadius:{get:function(){return z},set:function(a){z=a}},width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},showLabels:{get:function(){return l},set:function(a){l=a}},title:{get:function(){return q},set:function(a){q=a}},titleOffset:{get:function(){return s},set:function(a){s=a}},labelThreshold:{get:function(){return o},set:function(a){o=a}},valueFormat:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return h},set:function(a){h=a}},endAngle:{get:function(){return w},set:function(a){w=a}},startAngle:{get:function(){return u},set:function(a){u=a}},padAngle:{get:function(){return v},set:function(a){v=a}},cornerRadius:{get:function(){return x},set:function(a){x=a}},donutRatio:{get:function(){return y},set:function(a){y=a}},labelsOutside:{get:function(){return m},set:function(a){m=a}},labelSunbeamLayout:{get:function(){return t},set:function(a){t=a}},donut:{get:function(){return p},set:function(a){p=a}},growOnHover:{get:function(){return r},set:function(a){r=a}},pieLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("pieLabelsOutside","use labelsOutside instead")}},donutLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("donutLabelsOutside","use labelsOutside instead")}},labelFormat:{get:function(){return k},set:function(b){k=b,a.deprecated("labelFormat","use valueFormat instead")}},margin:{get:function(){return c},set:function(a){c.top="undefined"!=typeof a.top?a.top:c.top,c.right="undefined"!=typeof a.right?a.right:c.right,c.bottom="undefined"!=typeof a.bottom?a.bottom:c.bottom,c.left="undefined"!=typeof a.left?a.left:c.left}},y:{get:function(){return g},set:function(a){g=d3.functor(a)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},labelType:{get:function(){return n},set:function(a){n=a||"key"}}}),a.utils.initOptions(b),b},a.models.pieChart=function(){"use strict";function b(e){return q.reset(),q.models(c),e.each(function(e){var k=d3.select(this);a.utils.initSVG(k);var n=a.utils.availableWidth(g,k,f),o=a.utils.availableHeight(h,k,f);if(b.update=function(){k.transition().call(b)},b.container=this,l.setter(s(e),b.update).getter(r(e)).update(),l.disabled=e.map(function(a){return!!a.disabled}),!m){var q;m={};for(q in l)m[q]=l[q]instanceof Array?l[q].slice(0):l[q]}if(!e||!e.length)return a.utils.noData(b,k),b;k.selectAll(".nv-noData").remove();var t=k.selectAll("g.nv-wrap.nv-pieChart").data([e]),u=t.enter().append("g").attr("class","nvd3 nv-wrap nv-pieChart").append("g"),v=t.select("g");if(u.append("g").attr("class","nv-pieWrap"),u.append("g").attr("class","nv-legendWrap"),i)if("top"===j)d.width(n).key(c.x()),t.select(".nv-legendWrap").datum(e).call(d),f.top!=d.height()&&(f.top=d.height(),o=a.utils.availableHeight(h,k,f)),t.select(".nv-legendWrap").attr("transform","translate(0,"+-f.top+")");else if("right"===j){var w=a.models.legend().width();w>n/2&&(w=n/2),d.height(o).key(c.x()),d.width(w),n-=d.width(),t.select(".nv-legendWrap").datum(e).call(d).attr("transform","translate("+n+",0)")}t.attr("transform","translate("+f.left+","+f.top+")"),c.width(n).height(o);var x=v.select(".nv-pieWrap").datum([e]);d3.transition(x).call(c),d.dispatch.on("stateChange",function(a){for(var c in a)l[c]=a[c];p.stateChange(l),b.update()}),p.on("changeState",function(a){"undefined"!=typeof a.disabled&&(e.forEach(function(b,c){b.disabled=a.disabled[c]}),l.disabled=a.disabled),b.update()})}),q.renderEnd("pieChart immediate"),b}var c=a.models.pie(),d=a.models.legend(),e=a.models.tooltip(),f={top:30,right:20,bottom:20,left:20},g=null,h=null,i=!0,j="top",k=a.utils.defaultColor(),l=a.utils.state(),m=null,n=null,o=250,p=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd");e.headerEnabled(!1).duration(0).valueFormatter(function(a,b){return c.valueFormat()(a,b)});var q=a.utils.renderWatch(p),r=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},s=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},e.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){e.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){e.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.legend=d,b.dispatch=p,b.pie=c,b.tooltip=e,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return i},set:function(a){i=a}},legendPosition:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return e.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),e.enabled(!!b)}},tooltipContent:{get:function(){return e.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),e.contentGenerator(b)}},color:{get:function(){return k},set:function(a){k=a,d.color(k),c.color(k)}},duration:{get:function(){return o},set:function(a){o=a,q.reset(o)}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.scatter=function(){"use strict";function b(N){return P.reset(),N.each(function(b){function N(){if(O=!1,!w)return!1;if(M===!0){var a=d3.merge(b.map(function(a,b){return a.values.map(function(a,c){var d=p(a,c),e=q(a,c);return[m(d)+1e-4*Math.random(),n(e)+1e-4*Math.random(),b,c,a]}).filter(function(a,b){return x(a[4],b)})}));if(0==a.length)return!1;a.length<3&&(a.push([m.range()[0]-20,n.range()[0]-20,null,null]),a.push([m.range()[1]+20,n.range()[1]+20,null,null]),a.push([m.range()[0]-20,n.range()[0]+20,null,null]),a.push([m.range()[1]+20,n.range()[1]-20,null,null]));var c=d3.geom.polygon([[-10,-10],[-10,i+10],[h+10,i+10],[h+10,-10]]),d=d3.geom.voronoi(a).map(function(b,d){return{data:c.clip(b),series:a[d][2],point:a[d][3]}});U.select(".nv-point-paths").selectAll("path").remove();var e=U.select(".nv-point-paths").selectAll("path").data(d),f=e.enter().append("svg:path").attr("d",function(a){return a&&a.data&&0!==a.data.length?"M"+a.data.join(",")+"Z":"M 0 0"}).attr("id",function(a,b){return"nv-path-"+b}).attr("clip-path",function(a,b){return"url(#nv-clip-"+b+")"});C&&f.style("fill",d3.rgb(230,230,230)).style("fill-opacity",.4).style("stroke-opacity",1).style("stroke",d3.rgb(200,200,200)),B&&(U.select(".nv-point-clips").selectAll("clipPath").remove(),U.select(".nv-point-clips").selectAll("clipPath").data(a).enter().append("svg:clipPath").attr("id",function(a,b){return"nv-clip-"+b}).append("svg:circle").attr("cx",function(a){return a[0]}).attr("cy",function(a){return a[1]}).attr("r",D));var k=function(a,c){if(O)return 0;var d=b[a.series];if(void 0!==d){var e=d.values[a.point];e.color=j(d,a.series),e.x=p(e),e.y=q(e);var f=l.node().getBoundingClientRect(),h=window.pageYOffset||document.documentElement.scrollTop,i=window.pageXOffset||document.documentElement.scrollLeft,k={left:m(p(e,a.point))+f.left+i+g.left+10,top:n(q(e,a.point))+f.top+h+g.top+10};c({point:e,series:d,pos:k,seriesIndex:a.series,pointIndex:a.point})}};e.on("click",function(a){k(a,L.elementClick)}).on("dblclick",function(a){k(a,L.elementDblClick)}).on("mouseover",function(a){k(a,L.elementMouseover)}).on("mouseout",function(a){k(a,L.elementMouseout)})}else U.select(".nv-groups").selectAll(".nv-group").selectAll(".nv-point").on("click",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("dblclick",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementDblClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("mouseover",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseover({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c,color:j(a,c)})}).on("mouseout",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseout({point:e,series:d,seriesIndex:a.series,pointIndex:c,color:j(a,c)})})}l=d3.select(this);var R=a.utils.availableWidth(h,l,g),S=a.utils.availableHeight(i,l,g);a.utils.initSVG(l),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var T=E&&F&&I?[]:d3.merge(b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),size:r(a,b)}})}));m.domain(E||d3.extent(T.map(function(a){return a.x}).concat(t))),m.range(y&&b[0]?G||[(R*z+R)/(2*b[0].values.length),R-R*(1+z)/(2*b[0].values.length)]:G||[0,R]),n.domain(F||d3.extent(T.map(function(a){return a.y}).concat(u))).range(H||[S,0]),o.domain(I||d3.extent(T.map(function(a){return a.size}).concat(v))).range(J||Q),K=m.domain()[0]===m.domain()[1]||n.domain()[0]===n.domain()[1],m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]:[-1,1]),n.domain()[0]===n.domain()[1]&&n.domain(n.domain()[0]?[n.domain()[0]-.01*n.domain()[0],n.domain()[1]+.01*n.domain()[1]]:[-1,1]),isNaN(m.domain()[0])&&m.domain([-1,1]),isNaN(n.domain()[0])&&n.domain([-1,1]),c=c||m,d=d||n,e=e||o;var U=l.selectAll("g.nv-wrap.nv-scatter").data([b]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-scatter nv-chart-"+k),W=V.append("defs"),X=V.append("g"),Y=U.select("g");U.classed("nv-single-point",K),X.append("g").attr("class","nv-groups"),X.append("g").attr("class","nv-point-paths"),V.append("g").attr("class","nv-point-clips"),U.attr("transform","translate("+g.left+","+g.top+")"),W.append("clipPath").attr("id","nv-edge-clip-"+k).append("rect"),U.select("#nv-edge-clip-"+k+" rect").attr("width",R).attr("height",S>0?S:0),Y.attr("clip-path",A?"url(#nv-edge-clip-"+k+")":""),O=!0;var Z=U.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});Z.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),Z.exit().remove(),Z.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),Z.watchTransition(P,"scatter: groups").style("fill",function(a,b){return j(a,b)}).style("stroke",function(a,b){return j(a,b)}).style("stroke-opacity",1).style("fill-opacity",.5);var $=Z.selectAll("path.nv-point").data(function(a){return a.values.map(function(a,b){return[a,b]}).filter(function(a,b){return x(a[0],b)})});$.enter().append("path").style("fill",function(a){return a.color}).style("stroke",function(a){return a.color}).attr("transform",function(a){return"translate("+c(p(a[0],a[1]))+","+d(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),$.exit().remove(),Z.exit().selectAll("path.nv-point").watchTransition(P,"scatter exit").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).remove(),$.each(function(a){d3.select(this).classed("nv-point",!0).classed("nv-point-"+a[1],!0).classed("nv-noninteractive",!w).classed("hover",!1)}),$.watchTransition(P,"scatter points").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),clearTimeout(f),f=setTimeout(N,300),c=m.copy(),d=n.copy(),e=o.copy()}),P.renderEnd("scatter immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=a.utils.defaultColor(),k=Math.floor(1e5*Math.random()),l=null,m=d3.scale.linear(),n=d3.scale.linear(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=function(a){return a.size||1},s=function(a){return a.shape||"circle"},t=[],u=[],v=[],w=!0,x=function(a){return!a.notActive},y=!1,z=.1,A=!1,B=!0,C=!1,D=function(){return 25},E=null,F=null,G=null,H=null,I=null,J=null,K=!1,L=d3.dispatch("elementClick","elementDblClick","elementMouseover","elementMouseout","renderEnd"),M=!0,N=250,O=!1,P=a.utils.renderWatch(L,N),Q=[16,256];return b.dispatch=L,b.options=a.utils.optionsFunc.bind(b),b._calls=new function(){this.clearHighlights=function(){return a.dom.write(function(){l.selectAll(".nv-point.hover").classed("hover",!1)}),null},this.highlightPoint=function(b,c,d){a.dom.write(function(){l.select(" .nv-series-"+b+" .nv-point-"+c).classed("hover",d)})}},L.on("elementMouseover.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!0)}),L.on("elementMouseout.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!1)}),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},pointScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return E},set:function(a){E=a}},yDomain:{get:function(){return F},set:function(a){F=a}},pointDomain:{get:function(){return I},set:function(a){I=a}},xRange:{get:function(){return G},set:function(a){G=a}},yRange:{get:function(){return H},set:function(a){H=a}},pointRange:{get:function(){return J},set:function(a){J=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},forcePoint:{get:function(){return v},set:function(a){v=a}},interactive:{get:function(){return w},set:function(a){w=a}},pointActive:{get:function(){return x},set:function(a){x=a}},padDataOuter:{get:function(){return z},set:function(a){z=a}},padData:{get:function(){return y},set:function(a){y=a}},clipEdge:{get:function(){return A},set:function(a){A=a}},clipVoronoi:{get:function(){return B},set:function(a){B=a}},clipRadius:{get:function(){return D},set:function(a){D=a}},showVoronoi:{get:function(){return C},set:function(a){C=a}},id:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return p},set:function(a){p=d3.functor(a)}},y:{get:function(){return q},set:function(a){q=d3.functor(a)}},pointSize:{get:function(){return r},set:function(a){r=d3.functor(a)}},pointShape:{get:function(){return s},set:function(a){s=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},duration:{get:function(){return N},set:function(a){N=a,P.reset(N)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},useVoronoi:{get:function(){return M},set:function(a){M=a,M===!1&&(B=!1)}}}),a.utils.initOptions(b),b},a.models.scatterChart=function(){"use strict";function b(z){return D.reset(),D.models(c),t&&D.models(d),u&&D.models(e),q&&D.models(g),r&&D.models(h),z.each(function(z){m=d3.select(this),a.utils.initSVG(m);var G=a.utils.availableWidth(k,m,j),H=a.utils.availableHeight(l,m,j);if(b.update=function(){0===A?m.call(b):m.transition().duration(A).call(b)},b.container=this,w.setter(F(z),b.update).getter(E(z)).update(),w.disabled=z.map(function(a){return!!a.disabled}),!x){var I;x={};for(I in w)x[I]=w[I]instanceof Array?w[I].slice(0):w[I]}if(!(z&&z.length&&z.filter(function(a){return a.values.length}).length))return a.utils.noData(b,m),D.renderEnd("scatter immediate"),b;m.selectAll(".nv-noData").remove(),o=c.xScale(),p=c.yScale();var J=m.selectAll("g.nv-wrap.nv-scatterChart").data([z]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-scatterChart nv-chart-"+c.id()),L=K.append("g"),M=J.select("g");if(L.append("rect").attr("class","nvd3 nv-background").style("pointer-events","none"),L.append("g").attr("class","nv-x nv-axis"),L.append("g").attr("class","nv-y nv-axis"),L.append("g").attr("class","nv-scatterWrap"),L.append("g").attr("class","nv-regressionLinesWrap"),L.append("g").attr("class","nv-distWrap"),L.append("g").attr("class","nv-legendWrap"),v&&M.select(".nv-y.nv-axis").attr("transform","translate("+G+",0)"),s){var N=G;f.width(N),J.select(".nv-legendWrap").datum(z).call(f),j.top!=f.height()&&(j.top=f.height(),H=a.utils.availableHeight(l,m,j)),J.select(".nv-legendWrap").attr("transform","translate(0,"+-j.top+")")}J.attr("transform","translate("+j.left+","+j.top+")"),c.width(G).height(H).color(z.map(function(a,b){return a.color=a.color||n(a,b),a.color}).filter(function(a,b){return!z[b].disabled})),J.select(".nv-scatterWrap").datum(z.filter(function(a){return!a.disabled})).call(c),J.select(".nv-regressionLinesWrap").attr("clip-path","url(#nv-edge-clip-"+c.id()+")");var O=J.select(".nv-regressionLinesWrap").selectAll(".nv-regLines").data(function(a){return a});O.enter().append("g").attr("class","nv-regLines");var P=O.selectAll(".nv-regLine").data(function(a){return[a]});P.enter().append("line").attr("class","nv-regLine").style("stroke-opacity",0),P.filter(function(a){return a.intercept&&a.slope}).watchTransition(D,"scatterPlusLineChart: regline").attr("x1",o.range()[0]).attr("x2",o.range()[1]).attr("y1",function(a){return p(o.domain()[0]*a.slope+a.intercept)}).attr("y2",function(a){return p(o.domain()[1]*a.slope+a.intercept)}).style("stroke",function(a,b,c){return n(a,c)}).style("stroke-opacity",function(a){return a.disabled||"undefined"==typeof a.slope||"undefined"==typeof a.intercept?0:1}),t&&(d.scale(o)._ticks(a.utils.calcTicksX(G/100,z)).tickSize(-H,0),M.select(".nv-x.nv-axis").attr("transform","translate(0,"+p.range()[0]+")").call(d)),u&&(e.scale(p)._ticks(a.utils.calcTicksY(H/36,z)).tickSize(-G,0),M.select(".nv-y.nv-axis").call(e)),q&&(g.getData(c.x()).scale(o).width(G).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionX"),M.select(".nv-distributionX").attr("transform","translate(0,"+p.range()[0]+")").datum(z.filter(function(a){return!a.disabled})).call(g)),r&&(h.getData(c.y()).scale(p).width(H).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionY"),M.select(".nv-distributionY").attr("transform","translate("+(v?G:-h.size())+",0)").datum(z.filter(function(a){return!a.disabled})).call(h)),f.dispatch.on("stateChange",function(a){for(var c in a)w[c]=a[c];y.stateChange(w),b.update()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&(z.forEach(function(b,c){b.disabled=a.disabled[c]}),w.disabled=a.disabled),b.update()}),c.dispatch.on("elementMouseout.tooltip",function(a){i.hidden(!0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",h.size())}),c.dispatch.on("elementMouseover.tooltip",function(a){m.select(".nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",a.pos.top-H-j.top),m.select(".nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",a.pos.left+g.size()-j.left),i.position(a.pos).data(a).hidden(!1)}),B=o.copy(),C=p.copy()}),D.renderEnd("scatter with line immediate"),b}var c=a.models.scatter(),d=a.models.axis(),e=a.models.axis(),f=a.models.legend(),g=a.models.distribution(),h=a.models.distribution(),i=a.models.tooltip(),j={top:30,right:20,bottom:50,left:75},k=null,l=null,m=null,n=a.utils.defaultColor(),o=c.xScale(),p=c.yScale(),q=!1,r=!1,s=!0,t=!0,u=!0,v=!1,w=a.utils.state(),x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=null,A=250;c.xScale(o).yScale(p),d.orient("bottom").tickPadding(10),e.orient(v?"right":"left").tickPadding(10),g.axis("x"),h.axis("y"),i.headerFormatter(function(a,b){return d.tickFormat()(a,b)}).valueFormatter(function(a,b){return e.tickFormat()(a,b)});var B,C,D=a.utils.renderWatch(y,A),E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return b.dispatch=y,b.scatter=c,b.legend=f,b.xAxis=d,b.yAxis=e,b.distX=g,b.distY=h,b.tooltip=i,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},container:{get:function(){return m},set:function(a){m=a}},showDistX:{get:function(){return q},set:function(a){q=a}},showDistY:{get:function(){return r},set:function(a){r=a}},showLegend:{get:function(){return s},set:function(a){s=a}},showXAxis:{get:function(){return t},set:function(a){t=a}},showYAxis:{get:function(){return u},set:function(a){u=a}},defaultState:{get:function(){return x},set:function(a){x=a}},noData:{get:function(){return z},set:function(a){z=a}},duration:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return i.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),i.enabled(!!b)
+}},tooltipContent:{get:function(){return i.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),i.contentGenerator(b)}},tooltipXContent:{get:function(){return i.contentGenerator()},set:function(){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},tooltipYContent:{get:function(){return i.contentGenerator()},set:function(){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},rightAlignYAxis:{get:function(){return v},set:function(a){v=a,e.orient(a?"right":"left")}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),f.color(n),g.color(n),h.color(n)}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.sparkline=function(){"use strict";function b(k){return k.each(function(b){var k=h-g.left-g.right,q=i-g.top-g.bottom;j=d3.select(this),a.utils.initSVG(j),l.domain(c||d3.extent(b,n)).range(e||[0,k]),m.domain(d||d3.extent(b,o)).range(f||[q,0]);{var r=j.selectAll("g.nv-wrap.nv-sparkline").data([b]),s=r.enter().append("g").attr("class","nvd3 nv-wrap nv-sparkline");s.append("g"),r.select("g")}r.attr("transform","translate("+g.left+","+g.top+")");var t=r.selectAll("path").data(function(a){return[a]});t.enter().append("path"),t.exit().remove(),t.style("stroke",function(a,b){return a.color||p(a,b)}).attr("d",d3.svg.line().x(function(a,b){return l(n(a,b))}).y(function(a,b){return m(o(a,b))}));var u=r.selectAll("circle.nv-point").data(function(a){function b(b){if(-1!=b){var c=a[b];return c.pointIndex=b,c}return null}var c=a.map(function(a,b){return o(a,b)}),d=b(c.lastIndexOf(m.domain()[1])),e=b(c.indexOf(m.domain()[0])),f=b(c.length-1);return[e,d,f].filter(function(a){return null!=a})});u.enter().append("circle"),u.exit().remove(),u.attr("cx",function(a){return l(n(a,a.pointIndex))}).attr("cy",function(a){return m(o(a,a.pointIndex))}).attr("r",2).attr("class",function(a){return n(a,a.pointIndex)==l.domain()[1]?"nv-point nv-currentValue":o(a,a.pointIndex)==m.domain()[0]?"nv-point nv-minValue":"nv-point nv-maxValue"})}),b}var c,d,e,f,g={top:2,right:0,bottom:2,left:0},h=400,i=32,j=null,k=!0,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=a.utils.getColor(["#000"]);return b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},animate:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return n},set:function(a){n=d3.functor(a)}},y:{get:function(){return o},set:function(a){o=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return p},set:function(b){p=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sparklinePlus=function(){"use strict";function b(p){return p.each(function(p){function q(){if(!j){var a=z.selectAll(".nv-hoverValue").data(i),b=a.enter().append("g").attr("class","nv-hoverValue").style("stroke-opacity",0).style("fill-opacity",0);a.exit().transition().duration(250).style("stroke-opacity",0).style("fill-opacity",0).remove(),a.attr("transform",function(a){return"translate("+c(e.x()(p[a],a))+",0)"}).transition().duration(250).style("stroke-opacity",1).style("fill-opacity",1),i.length&&(b.append("line").attr("x1",0).attr("y1",-f.top).attr("x2",0).attr("y2",u),b.append("text").attr("class","nv-xValue").attr("x",-6).attr("y",-f.top).attr("text-anchor","end").attr("dy",".9em"),z.select(".nv-hoverValue .nv-xValue").text(k(e.x()(p[i[0]],i[0]))),b.append("text").attr("class","nv-yValue").attr("x",6).attr("y",-f.top).attr("text-anchor","start").attr("dy",".9em"),z.select(".nv-hoverValue .nv-yValue").text(l(e.y()(p[i[0]],i[0]))))}}function r(){function a(a,b){for(var c=Math.abs(e.x()(a[0],0)-b),d=0,f=0;fc;++c){for(b=0,d=0;bb;b++)a[b][c][1]/=d;else for(b=0;e>b;b++)a[b][c][1]=0}for(c=0;f>c;++c)g[c]=0;return g}}),u.renderEnd("stackedArea immediate"),b}var c,d,e={top:0,right:0,bottom:0,left:0},f=960,g=500,h=a.utils.defaultColor(),i=Math.floor(1e5*Math.random()),j=null,k=function(a){return a.x},l=function(a){return a.y},m="stack",n="zero",o="default",p="linear",q=!1,r=a.models.scatter(),s=250,t=d3.dispatch("areaClick","areaMouseover","areaMouseout","renderEnd","elementClick","elementMouseover","elementMouseout");r.pointSize(2.2).pointDomain([2.2,2.2]);var u=a.utils.renderWatch(t,s);return b.dispatch=t,b.scatter=r,r.dispatch.on("elementClick",function(){t.elementClick.apply(this,arguments)}),r.dispatch.on("elementMouseover",function(){t.elementMouseover.apply(this,arguments)}),r.dispatch.on("elementMouseout",function(){t.elementMouseout.apply(this,arguments)}),b.interpolate=function(a){return arguments.length?(p=a,b):p},b.duration=function(a){return arguments.length?(s=a,u.reset(s),r.duration(s),b):s},b.dispatch=t,b.scatter=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return f},set:function(a){f=a}},height:{get:function(){return g},set:function(a){g=a}},clipEdge:{get:function(){return q},set:function(a){q=a}},offset:{get:function(){return n},set:function(a){n=a}},order:{get:function(){return o},set:function(a){o=a}},interpolate:{get:function(){return p},set:function(a){p=a}},x:{get:function(){return k},set:function(a){k=d3.functor(a)}},y:{get:function(){return l},set:function(a){l=d3.functor(a)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return h},set:function(b){h=a.utils.getColor(b)}},style:{get:function(){return m},set:function(a){switch(m=a){case"stack":b.offset("zero"),b.order("default");break;case"stream":b.offset("wiggle"),b.order("inside-out");break;case"stream-center":b.offset("silhouette"),b.order("inside-out");break;case"expand":b.offset("expand"),b.order("default");break;case"stack_percent":b.offset(b.d3_stackedOffset_stackPercent),b.order("default")}}},duration:{get:function(){return s},set:function(a){s=a,u.reset(s),r.duration(s)}}}),a.utils.inheritOptions(b,r),a.utils.initOptions(b),b},a.models.stackedAreaChart=function(){"use strict";function b(k){return F.reset(),F.models(e),r&&F.models(f),s&&F.models(g),k.each(function(k){var x=d3.select(this),F=this;a.utils.initSVG(x);var K=a.utils.availableWidth(m,x,l),L=a.utils.availableHeight(n,x,l);if(b.update=function(){x.transition().duration(C).call(b)},b.container=this,v.setter(I(k),b.update).getter(H(k)).update(),v.disabled=k.map(function(a){return!!a.disabled}),!w){var M;w={};for(M in v)w[M]=v[M]instanceof Array?v[M].slice(0):v[M]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(b,x),b;x.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var N=x.selectAll("g.nv-wrap.nv-stackedAreaChart").data([k]),O=N.enter().append("g").attr("class","nvd3 nv-wrap nv-stackedAreaChart").append("g"),P=N.select("g");if(O.append("rect").style("opacity",0),O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y nv-axis"),O.append("g").attr("class","nv-stackedWrap"),O.append("g").attr("class","nv-legendWrap"),O.append("g").attr("class","nv-controlsWrap"),O.append("g").attr("class","nv-interactive"),P.select("rect").attr("width",K).attr("height",L),q){var Q=p?K-z:K;h.width(Q),P.select(".nv-legendWrap").datum(k).call(h),l.top!=h.height()&&(l.top=h.height(),L=a.utils.availableHeight(n,x,l)),P.select(".nv-legendWrap").attr("transform","translate("+(K-Q)+","+-l.top+")")}if(p){var R=[{key:B.stacked||"Stacked",metaKey:"Stacked",disabled:"stack"!=e.style(),style:"stack"},{key:B.stream||"Stream",metaKey:"Stream",disabled:"stream"!=e.style(),style:"stream"},{key:B.expanded||"Expanded",metaKey:"Expanded",disabled:"expand"!=e.style(),style:"expand"},{key:B.stack_percent||"Stack %",metaKey:"Stack_Percent",disabled:"stack_percent"!=e.style(),style:"stack_percent"}];z=A.length/3*260,R=R.filter(function(a){return-1!==A.indexOf(a.metaKey)}),i.width(z).color(["#444","#444","#444"]),P.select(".nv-controlsWrap").datum(R).call(i),l.top!=Math.max(i.height(),h.height())&&(l.top=Math.max(i.height(),h.height()),L=a.utils.availableHeight(n,x,l)),P.select(".nv-controlsWrap").attr("transform","translate(0,"+-l.top+")")}N.attr("transform","translate("+l.left+","+l.top+")"),t&&P.select(".nv-y.nv-axis").attr("transform","translate("+K+",0)"),u&&(j.width(K).height(L).margin({left:l.left,top:l.top}).svgContainer(x).xScale(c),N.select(".nv-interactive").call(j)),e.width(K).height(L);var S=P.select(".nv-stackedWrap").datum(k);if(S.transition().call(e),r&&(f.scale(c)._ticks(a.utils.calcTicksX(K/100,k)).tickSize(-L,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+L+")"),P.select(".nv-x.nv-axis").transition().duration(0).call(f)),s){var T;if(T="wiggle"===e.offset()?0:a.utils.calcTicksY(L/36,k),g.scale(d)._ticks(T).tickSize(-K,0),"expand"===e.style()||"stack_percent"===e.style()){var U=g.tickFormat();D&&U===J||(D=U),g.tickFormat(J)}else D&&(g.tickFormat(D),D=null);P.select(".nv-y.nv-axis").transition().duration(0).call(g)}e.dispatch.on("areaClick.toggle",function(a){k.forEach(1===k.filter(function(a){return!a.disabled}).length?function(a){a.disabled=!1}:function(b,c){b.disabled=c!=a.seriesIndex}),v.disabled=k.map(function(a){return!!a.disabled}),y.stateChange(v),b.update()}),h.dispatch.on("stateChange",function(a){for(var c in a)v[c]=a[c];y.stateChange(v),b.update()}),i.dispatch.on("legendClick",function(a){a.disabled&&(R=R.map(function(a){return a.disabled=!0,a}),a.disabled=!1,e.style(a.style),v.style=e.style(),y.stateChange(v),b.update())}),j.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,g,h,i=[];if(k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,j){g=a.interactiveBisect(f.values,c.pointXValue,b.x());var k=f.values[g],l=b.y()(k,g);if(null!=l&&e.highlightPoint(j,g,!0),"undefined"!=typeof k){"undefined"==typeof d&&(d=k),"undefined"==typeof h&&(h=b.xScale()(b.x()(k,g)));var m="expand"==e.style()?k.display.y:b.y()(k,g);i.push({key:f.key,value:m,color:o(f,f.seriesIndex),stackedValue:k.display})}}),i.reverse(),i.length>2){var m=b.yScale().invert(c.mouseY),n=null;i.forEach(function(a,b){m=Math.abs(m);var c=Math.abs(a.stackedValue.y0),d=Math.abs(a.stackedValue.y);return m>=c&&d+c>=m?void(n=b):void 0}),null!=n&&(i[n].highlight=!0)}var p=f.tickFormat()(b.x()(d,g)),q=j.tooltip.valueFormatter();"expand"===e.style()||"stack_percent"===e.style()?(E||(E=q),q=d3.format(".1%")):E&&(q=E,E=null),j.tooltip.position({left:h+l.left,top:c.mouseY+l.top}).chartContainer(F.parentNode).valueFormatter(q).data({value:p,series:i})(),j.renderGuideLine(h)}),j.dispatch.on("elementMouseout",function(){e.clearHighlights()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&k.length===a.disabled.length&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),v.disabled=a.disabled),"undefined"!=typeof a.style&&(e.style(a.style),G=a.style),b.update()})}),F.renderEnd("stacked Area chart immediate"),b}var c,d,e=a.models.stackedArea(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:25,bottom:50,left:60},m=null,n=null,o=a.utils.defaultColor(),p=!0,q=!0,r=!0,s=!0,t=!1,u=!1,v=a.utils.state(),w=null,x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=250,A=["Stacked","Stream","Expanded"],B={},C=250;v.style=e.style(),f.orient("bottom").tickPadding(7),g.orient(t?"right":"left"),k.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)}),j.tooltip.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)});var D=null,E=null;i.updateState(!1);var F=a.utils.renderWatch(y),G=e.style(),H=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),style:e.style()}}},I=function(a){return function(b){void 0!==b.style&&(G=b.style),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},J=d3.format("%");return e.dispatch.on("elementMouseover.tooltip",function(a){a.point.x=e.x()(a.point),a.point.y=e.y()(a.point),k.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){k.hidden(!0)}),b.dispatch=y,b.stacked=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.interactiveLayer=j,b.tooltip=k,b.dispatch=y,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return w},set:function(a){w=a}},noData:{get:function(){return x},set:function(a){x=a}},showControls:{get:function(){return p},set:function(a){p=a}},controlLabels:{get:function(){return B},set:function(a){B=a}},controlOptions:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},duration:{get:function(){return C},set:function(a){C=a,F.reset(C),e.duration(C),f.duration(C),g.duration(C)}},color:{get:function(){return o},set:function(b){o=a.utils.getColor(b),h.color(o),e.color(o)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},useInteractiveGuideline:{get:function(){return u},set:function(a){u=!!a,b.interactive(!a),b.useVoronoi(!a),e.scatter.interactive(!a)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.sunburst=function(){"use strict";function b(u){return t.reset(),u.each(function(b){function t(a){a.x0=a.x,a.dx0=a.dx}function u(a){var b=d3.interpolate(p.domain(),[a.x,a.x+a.dx]),c=d3.interpolate(q.domain(),[a.y,1]),d=d3.interpolate(q.range(),[a.y?20:0,y]);return function(a,e){return e?function(){return s(a)}:function(e){return p.domain(b(e)),q.domain(c(e)).range(d(e)),s(a)}}}l=d3.select(this);var v,w=a.utils.availableWidth(g,l,f),x=a.utils.availableHeight(h,l,f),y=Math.min(w,x)/2;a.utils.initSVG(l);var z=l.selectAll(".nv-wrap.nv-sunburst").data(b),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburst nv-chart-"+k),B=A.selectAll("nv-sunburst");z.attr("transform","translate("+w/2+","+x/2+")"),l.on("click",function(a,b){o.chartClick({data:a,index:b,pos:d3.event,id:k})}),q.range([0,y]),c=c||b,e=b[0],r.value(j[i]||j.count),v=B.data(r.nodes).enter().append("path").attr("d",s).style("fill",function(a){return m((a.children?a:a.parent).name)}).style("stroke","#FFF").on("click",function(a){d!==c&&c!==a&&(d=c),c=a,v.transition().duration(n).attrTween("d",u(a))}).each(t).on("dblclick",function(a){d.parent==a&&v.transition().duration(n).attrTween("d",u(e))}).each(t).on("mouseover",function(a){d3.select(this).classed("hover",!0).style("opacity",.8),o.elementMouseover({data:a,color:d3.select(this).style("fill")})}).on("mouseout",function(a){d3.select(this).classed("hover",!1).style("opacity",1),o.elementMouseout({data:a})}).on("mousemove",function(a){o.elementMousemove({data:a})})}),t.renderEnd("sunburst immediate"),b}var c,d,e,f={top:0,right:0,bottom:0,left:0},g=null,h=null,i="count",j={count:function(){return 1},size:function(a){return a.size}},k=Math.floor(1e4*Math.random()),l=null,m=a.utils.defaultColor(),n=500,o=d3.dispatch("chartClick","elementClick","elementDblClick","elementMousemove","elementMouseover","elementMouseout","renderEnd"),p=d3.scale.linear().range([0,2*Math.PI]),q=d3.scale.sqrt(),r=d3.layout.partition().sort(null).value(function(){return 1}),s=d3.svg.arc().startAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x)))}).endAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x+a.dx)))}).innerRadius(function(a){return Math.max(0,q(a.y))}).outerRadius(function(a){return Math.max(0,q(a.y+a.dy))}),t=a.utils.renderWatch(o);return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},mode:{get:function(){return i},set:function(a){i=a}},id:{get:function(){return k},set:function(a){k=a}},duration:{get:function(){return n},set:function(a){n=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!=a.top?a.top:f.top,f.right=void 0!=a.right?a.right:f.right,f.bottom=void 0!=a.bottom?a.bottom:f.bottom,f.left=void 0!=a.left?a.left:f.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sunburstChart=function(){"use strict";function b(d){return m.reset(),m.models(c),d.each(function(d){var h=d3.select(this);a.utils.initSVG(h);var i=a.utils.availableWidth(f,h,e),j=a.utils.availableHeight(g,h,e);if(b.update=function(){0===k?h.call(b):h.transition().duration(k).call(b)},b.container=this,!d||!d.length)return a.utils.noData(b,h),b;h.selectAll(".nv-noData").remove();var l=h.selectAll("g.nv-wrap.nv-sunburstChart").data(d),m=l.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburstChart").append("g"),n=l.select("g");m.append("g").attr("class","nv-sunburstWrap"),l.attr("transform","translate("+e.left+","+e.top+")"),c.width(i).height(j);var o=n.select(".nv-sunburstWrap").datum(d);d3.transition(o).call(c)}),m.renderEnd("sunburstChart immediate"),b}var c=a.models.sunburst(),d=a.models.tooltip(),e={top:30,right:20,bottom:20,left:20},f=null,g=null,h=a.utils.defaultColor(),i=(Math.round(1e5*Math.random()),null),j=null,k=250,l=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),m=a.utils.renderWatch(l);return d.headerEnabled(!1).duration(0).valueFormatter(function(a){return a}),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.data.name,value:a.data.size,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=l,b.sunburst=c,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return i},set:function(a){i=a}},color:{get:function(){return h},set:function(a){h=a,c.color(h)}},duration:{get:function(){return k},set:function(a){k=a,m.reset(k),c.duration(k)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.version="1.8.1"}();
\ No newline at end of file
diff --git a/coverage/_js/popper.min.js b/coverage/_js/popper.min.js
new file mode 100644
index 00000000..bb1aaae3
--- /dev/null
+++ b/coverage/_js/popper.min.js
@@ -0,0 +1,5 @@
+/*
+ Copyright (C) Federico Zivolo 2020
+ Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
+ */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function i(e){return e&&e.referenceNode?e.referenceNode:e}function r(e){return 11===e?re:10===e?pe:re||pe}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),ae({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=le({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f]),E=parseFloat(w['border'+f+'Width']),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},ae(n,m,$(v)),ae(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ce.FLIP:p=[n,i];break;case ce.CLOCKWISE:p=G(n);break;case ce.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u),E=!!t.flipVariationsByContent&&(w&&'start'===r&&c||w&&'end'===r&&h||!w&&'start'===r&&u||!w&&'end'===r&&g),v=y||E;(m||b||v)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),v&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=le({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport',flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.rightwindow.devicePixelRatio||!fe),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=B('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=le({},E,e.attributes),e.styles=le({},m,e.styles),e.arrowStyles=le({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return V(e.instance.popper,e.styles),j(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&V(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),V(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ge});
+//# sourceMappingURL=popper.min.js.map
diff --git a/coverage/autoload.php.html b/coverage/autoload.php.html
new file mode 100644
index 00000000..f7348a14
--- /dev/null
+++ b/coverage/autoload.php.html
@@ -0,0 +1,103 @@
+
+
+
+
+ Code Coverage for C:\Users\ASUS\Desktop\proyecto-si784-2024-ii-u2-chambilla_llantay\src\autoload.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Code Coverage
+
+
+
+ Lines
+ Functions and Methods
+ Classes and Traits
+
+
+
+
+ Total
+
+
+ 25.00% covered (danger)
+
+
+
+ 25.00%
+ 2 / 8
+
+ n/a
+ 0 / 0
+ CRAP
+
+ n/a
+ 0 / 0
+
+
+
+
+
+
+
+
+ 1 <?php
+ 2 spl_autoload_register ( function ( $class ) {
+ 3
+ 4 $base_dir = __DIR__ . '/' ;
+ 5 $file = $base_dir . str_replace ( '\\' , '/' , $class ) . '.php' ;
+ 6
+ 7 if ( file_exists ( $file ) ) {
+ 8 require_once $file ;
+ 9 return true ;
+ 10 }
+ 11 return false ;
+ 12 } ) ;
+
+
+
+
+
+
+
+ Legend
+ Covered by small (and larger) tests Covered by medium (and large) tests Covered by large tests (and tests of unknown size) Not covered Not coverable
+
+ Generated by php-code-coverage 9.2.32 using PHP 8.2.12 and PHPUnit 9.6.21 at Thu Nov 28 18:26:22 CET 2024.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/coverage/dashboard.html b/coverage/dashboard.html
new file mode 100644
index 00000000..7c3dc8a0
--- /dev/null
+++ b/coverage/dashboard.html
@@ -0,0 +1,334 @@
+
+
+
+
+ Dashboard for C:\Users\ASUS\Desktop\proyecto-si784-2024-ii-u2-chambilla_llantay\src
+
+
+
+
+
+
+
+
+
+
+
+
+
Coverage Distribution
+
+
+
+
+
+
+
+
+
Insufficient Coverage
+
+
+
+
+
+
+
+
Coverage Distribution
+
+
+
+
+
+
+
+
+
Insufficient Coverage
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/coverage/index.html b/coverage/index.html
new file mode 100644
index 00000000..66f606ce
--- /dev/null
+++ b/coverage/index.html
@@ -0,0 +1,238 @@
+
+
+
+
+ Code Coverage for C:\Users\ASUS\Desktop\proyecto-si784-2024-ii-u2-chambilla_llantay\src
+
+
+
+
+
+
+
+
+
+
+
+
+
+ C:\Users\ASUS\Desktop\proyecto-si784-2024-ii-u2-chambilla_llantay\src
+ (Dashboard )
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Code Coverage
+
+
+
+ Lines
+ Functions and Methods
+ Classes and Traits
+
+
+
+
+ Total
+
+
+ 84.66% covered (warning)
+
+
+
+ 84.66%
+ 585 / 691
+
+
+ 81.54% covered (warning)
+
+
+
+ 81.54%
+ 106 / 130
+
+
+ 46.15% covered (danger)
+
+
+
+ 46.15%
+ 6 / 13
+
+
+
+ Config
+
+
+ 55.56% covered (warning)
+
+
+
+ 55.56%
+ 20 / 36
+
+
+ 25.00% covered (danger)
+
+
+
+ 25.00%
+ 1 / 4
+
+
+ 0.00% covered (danger)
+
+
+
+ 0.00%
+ 0 / 1
+
+
+
+ Controllers
+
+
+ 86.43% covered (warning)
+
+
+
+ 86.43%
+ 484 / 560
+
+
+ 69.49% covered (warning)
+
+
+
+ 69.49%
+ 41 / 59
+
+
+ 50.00% covered (danger)
+
+
+
+ 50.00%
+ 3 / 6
+
+
+
+ Exceptions
+
+
+ 100.00% covered (success)
+
+
+
+ 100.00%
+ 1 / 1
+
+
+ 100.00% covered (success)
+
+
+
+ 100.00%
+ 1 / 1
+
+
+ 100.00% covered (success)
+
+
+
+ 100.00%
+ 1 / 1
+
+
+
+ Models
+
+
+ 96.30% covered (success)
+
+
+
+ 96.30%
+ 78 / 81
+
+
+ 95.45% covered (success)
+
+
+
+ 95.45%
+ 63 / 66
+
+
+ 40.00% covered (danger)
+
+
+
+ 40.00%
+ 2 / 5
+
+
+
+ autoload.php
+
+
+ 25.00% covered (danger)
+
+
+
+ 25.00%
+ 2 / 8
+
+ n/a
+ 0 / 0
+
+ n/a
+ 0 / 0
+
+
+
+ index.php
+
+
+ 0.00% covered (danger)
+
+
+
+ 0.00%
+ 0 / 5
+
+ n/a
+ 0 / 0
+
+ n/a
+ 0 / 0
+
+
+
+
+
+
+
+
+
+
diff --git a/coverage/index.php.html b/coverage/index.php.html
new file mode 100644
index 00000000..7fe4d4b9
--- /dev/null
+++ b/coverage/index.php.html
@@ -0,0 +1,104 @@
+
+
+
+
+ Code Coverage for C:\Users\ASUS\Desktop\proyecto-si784-2024-ii-u2-chambilla_llantay\src\index.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Code Coverage
+
+
+
+ Lines
+ Functions and Methods
+ Classes and Traits
+
+
+
+
+ Total
+
+
+ 0.00% covered (danger)
+
+
+
+ 0.00%
+ 0 / 5
+
+ n/a
+ 0 / 0
+ CRAP
+
+ n/a
+ 0 / 0
+
+
+
+
+
+
+
+
+ 1 <?php
+ 2
+ 3 if ( isset ( $_SESSION [ 'user_id' ] ) ) {
+ 4 if ( $_SESSION [ 'user_type' ] == 'admin' ) {
+ 5 header ( 'location:views/admin/admin_page.php' ) ;
+ 6 } else {
+ 7 header ( 'location:views/home.php' ) ;
+ 8 }
+ 9 } else {
+ 10
+ 11 header ( 'location:views/auth/login.php' ) ;
+ 12 }
+ 13 ?>
+
+
+
+
+
+
+
+ Legend
+ Covered by small (and larger) tests Covered by medium (and large) tests Covered by large tests (and tests of unknown size) Not covered Not coverable
+
+ Generated by php-code-coverage 9.2.32 using PHP 8.2.12 and PHPUnit 9.6.21 at Thu Nov 28 18:26:22 CET 2024.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000..69f92ba7
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,68 @@
+version: '3'
+services:
+ web:
+ build: .
+ container_name: proyecto_codigo_web
+ ports:
+ - "8080:80"
+ volumes:
+ - ./src:/var/www/html
+ depends_on:
+ - db
+ networks:
+ - tu_red_docker
+
+ db:
+ image: mysql:8.0
+ container_name: tienda_mysql
+ environment:
+ MYSQL_ROOT_PASSWORD: "123456"
+ MYSQL_DATABASE: tienda_bd
+ MYSQL_ROOT_HOST: '%'
+ ports:
+ - "3306:3306"
+ volumes:
+ - mysql_data:/var/lib/mysql
+ command: --default-authentication-plugin=mysql_native_password --bind-address=0.0.0.0
+ networks:
+ - tu_red_docker
+
+ selenium-hub:
+ image: selenium/hub:latest
+ container_name: selenium-hub
+ ports:
+ - "4442:4442"
+ - "4443:4443"
+ - "4444:4444"
+ networks:
+ - tu_red_docker
+
+ chrome:
+ image: selenium/node-chrome:latest
+ shm_size: '2g'
+ depends_on:
+ - selenium-hub
+ environment:
+ - SE_EVENT_BUS_HOST=selenium-hub
+ - SE_EVENT_BUS_PUBLISH_PORT=4442
+ - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
+ - SE_NODE_MAX_SESSIONS=4
+ - SE_NODE_OVERRIDE_MAX_SESSIONS=true
+ - SE_VNC_NO_PASSWORD=1
+ - SE_SCREEN_WIDTH=1920
+ - SE_SCREEN_HEIGHT=1080
+ - SE_ENABLE_RECORDING=true
+ - SE_RECORD_VIDEO=true
+ volumes:
+ - ./test-videos:/opt/selenium/videos
+ ports:
+ - "7901:7900"
+ networks:
+ - tu_red_docker
+
+volumes:
+ mysql_data:
+
+networks:
+ tu_red_docker:
+ external: true
\ No newline at end of file
diff --git a/error_screenshot_2024-11-29_01-08-02.png b/error_screenshot_2024-11-29_01-08-02.png
new file mode 100644
index 00000000..83e42534
Binary files /dev/null and b/error_screenshot_2024-11-29_01-08-02.png differ
diff --git a/error_screenshot_2024-11-29_01-08-08.png b/error_screenshot_2024-11-29_01-08-08.png
new file mode 100644
index 00000000..96029755
Binary files /dev/null and b/error_screenshot_2024-11-29_01-08-08.png differ
diff --git a/error_screenshot_2024-11-29_01-11-12.png b/error_screenshot_2024-11-29_01-11-12.png
new file mode 100644
index 00000000..d5e4020a
Binary files /dev/null and b/error_screenshot_2024-11-29_01-11-12.png differ
diff --git a/media/Casos_Uso_Administrador.png b/media/Casos_Uso_Administrador.png
new file mode 100644
index 00000000..180a1481
Binary files /dev/null and b/media/Casos_Uso_Administrador.png differ
diff --git a/media/Casos_Uso_Usuario.png b/media/Casos_Uso_Usuario.png
new file mode 100644
index 00000000..cab1725b
Binary files /dev/null and b/media/Casos_Uso_Usuario.png differ
diff --git a/media/Coberrtura_SonarCloud.png b/media/Coberrtura_SonarCloud.png
new file mode 100644
index 00000000..53b8fd76
Binary files /dev/null and b/media/Coberrtura_SonarCloud.png differ
diff --git a/media/Comparativa_Snyk.png b/media/Comparativa_Snyk.png
new file mode 100644
index 00000000..5be5644e
Binary files /dev/null and b/media/Comparativa_Snyk.png differ
diff --git a/media/Comparativa_Sonar.png b/media/Comparativa_Sonar.png
new file mode 100644
index 00000000..0d991427
Binary files /dev/null and b/media/Comparativa_Sonar.png differ
diff --git a/media/Diagrama_Clases.png b/media/Diagrama_Clases.png
new file mode 100644
index 00000000..d54a93da
Binary files /dev/null and b/media/Diagrama_Clases.png differ
diff --git a/media/Diagrama_Componentes.png b/media/Diagrama_Componentes.png
new file mode 100644
index 00000000..65391952
Binary files /dev/null and b/media/Diagrama_Componentes.png differ
diff --git a/media/Diagrama_Despligu_Arquitectura.png b/media/Diagrama_Despligu_Arquitectura.png
new file mode 100644
index 00000000..78ba59f1
Binary files /dev/null and b/media/Diagrama_Despligu_Arquitectura.png differ
diff --git a/media/Diagrama_Secuencia_Buscar_Usuario.png b/media/Diagrama_Secuencia_Buscar_Usuario.png
new file mode 100644
index 00000000..a4b1c70b
Binary files /dev/null and b/media/Diagrama_Secuencia_Buscar_Usuario.png differ
diff --git a/media/Diagrama_Secuencia_Contacto_Usuario.png b/media/Diagrama_Secuencia_Contacto_Usuario.png
new file mode 100644
index 00000000..fe102d11
Binary files /dev/null and b/media/Diagrama_Secuencia_Contacto_Usuario.png differ
diff --git a/media/Diagrama_Secuencia_EstadoProducto_Usuario.png b/media/Diagrama_Secuencia_EstadoProducto_Usuario.png
new file mode 100644
index 00000000..1d5b641f
Binary files /dev/null and b/media/Diagrama_Secuencia_EstadoProducto_Usuario.png differ
diff --git a/media/Diagrama_Secuencia_Inicio_Admin.png b/media/Diagrama_Secuencia_Inicio_Admin.png
new file mode 100644
index 00000000..9e1c3475
Binary files /dev/null and b/media/Diagrama_Secuencia_Inicio_Admin.png differ
diff --git a/media/Diagrama_Secuencia_Mensajes_Admin.png b/media/Diagrama_Secuencia_Mensajes_Admin.png
new file mode 100644
index 00000000..3460e8ed
Binary files /dev/null and b/media/Diagrama_Secuencia_Mensajes_Admin.png differ
diff --git a/media/Diagrama_Secuencia_Ordenes_Admin.png b/media/Diagrama_Secuencia_Ordenes_Admin.png
new file mode 100644
index 00000000..9886d91c
Binary files /dev/null and b/media/Diagrama_Secuencia_Ordenes_Admin.png differ
diff --git a/media/Diagrama_Secuencia_Productos_Admin.png b/media/Diagrama_Secuencia_Productos_Admin.png
new file mode 100644
index 00000000..258ff705
Binary files /dev/null and b/media/Diagrama_Secuencia_Productos_Admin.png differ
diff --git a/media/Diagrama_Secuencia_Shop_Usuario.png b/media/Diagrama_Secuencia_Shop_Usuario.png
new file mode 100644
index 00000000..321458d8
Binary files /dev/null and b/media/Diagrama_Secuencia_Shop_Usuario.png differ
diff --git a/media/Diagrama_Secuencia_Usuarios_Admin.png b/media/Diagrama_Secuencia_Usuarios_Admin.png
new file mode 100644
index 00000000..b1cdf5a1
Binary files /dev/null and b/media/Diagrama_Secuencia_Usuarios_Admin.png differ
diff --git a/media/Semgrep_Antes.png b/media/Semgrep_Antes.png
new file mode 100644
index 00000000..8fdf505a
Binary files /dev/null and b/media/Semgrep_Antes.png differ
diff --git a/media/Semgrep_Despues.png b/media/Semgrep_Despues.png
new file mode 100644
index 00000000..5ad08e23
Binary files /dev/null and b/media/Semgrep_Despues.png differ
diff --git a/media/logo-upt.png b/media/logo-upt.png
new file mode 100644
index 00000000..de7c974e
Binary files /dev/null and b/media/logo-upt.png differ
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 00000000..b9cdd348
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ ./tests/Unit
+
+
+
+
+
+
+
+ ./src
+
+
+ ./src/views
+
+
+
\ No newline at end of file
diff --git a/reports/assets/Twig/css/callout.css b/reports/assets/Twig/css/callout.css
new file mode 100644
index 00000000..7a1a2f71
--- /dev/null
+++ b/reports/assets/Twig/css/callout.css
@@ -0,0 +1,56 @@
+.bs-callout {
+ padding: 20px;
+ margin: 20px 0;
+ border: 1px solid #eee;
+ border-left-width: 5px;
+ border-radius: 3px;
+}
+.bs-callout h4 {
+ margin-top: 0;
+ margin-bottom: 5px;
+}
+.bs-callout p:last-child {
+ margin-bottom: 0;
+}
+.bs-callout code {
+ border-radius: 3px;
+}
+.bs-callout + .bs-callout {
+ margin-top: -5px;
+}
+.bs-callout-default {
+ border-left-color: #777;
+}
+.bs-callout-default h4 {
+ color: #777;
+}
+.bs-callout-primary {
+ border-left-color: #428bca;
+}
+.bs-callout-primary h4 {
+ color: #428bca;
+}
+.bs-callout-success {
+ border-left-color: #5cb85c;
+}
+.bs-callout-success h4 {
+ color: #5cb85c;
+}
+.bs-callout-danger {
+ border-left-color: #d9534f;
+}
+.bs-callout-danger h4 {
+ color: #d9534f;
+}
+.bs-callout-warning {
+ border-left-color: #f0ad4e;
+}
+.bs-callout-warning h4 {
+ color: #f0ad4e;
+}
+.bs-callout-info {
+ border-left-color: #5bc0de;
+}
+.bs-callout-info h4 {
+ color: #5bc0de;
+}
diff --git a/reports/assets/Twig/css/callout.less b/reports/assets/Twig/css/callout.less
new file mode 100644
index 00000000..42353991
--- /dev/null
+++ b/reports/assets/Twig/css/callout.less
@@ -0,0 +1,56 @@
+.bs-callout {
+ padding: 20px;
+ margin: 20px 0;
+ border: 1px solid #eee;
+ border-left-width: 5px;
+ border-radius: 3px;
+}
+.bs-callout h4 {
+ margin-top: 0;
+ margin-bottom: 5px;
+}
+.bs-callout p:last-child {
+ margin-bottom: 0;
+}
+.bs-callout code {
+ border-radius: 3px;
+}
+.bs-callout+.bs-callout {
+ margin-top: -5px;
+}
+.bs-callout-default {
+ border-left-color: #777;
+}
+.bs-callout-default h4 {
+ color: #777;
+}
+.bs-callout-primary {
+ border-left-color: #428bca;
+}
+.bs-callout-primary h4 {
+ color: #428bca;
+}
+.bs-callout-success {
+ border-left-color: #5cb85c;
+}
+.bs-callout-success h4 {
+ color: #5cb85c;
+}
+.bs-callout-danger {
+ border-left-color: #d9534f;
+}
+.bs-callout-danger h4 {
+ color: #d9534f;
+}
+.bs-callout-warning {
+ border-left-color: #f0ad4e;
+}
+.bs-callout-warning h4 {
+ color: #f0ad4e;
+}
+.bs-callout-info {
+ border-left-color: #5bc0de;
+}
+.bs-callout-info h4 {
+ color: #5bc0de;
+}
diff --git a/reports/assets/Twig/css/style.css b/reports/assets/Twig/css/style.css
new file mode 100644
index 00000000..d71d5929
--- /dev/null
+++ b/reports/assets/Twig/css/style.css
@@ -0,0 +1,170 @@
+@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700);
+.bs-callout {
+ padding: 20px;
+ margin: 20px 0;
+ border: 1px solid #eee;
+ border-left-width: 5px;
+ border-radius: 3px;
+}
+.bs-callout h4 {
+ margin-top: 0;
+ margin-bottom: 5px;
+}
+.bs-callout p:last-child {
+ margin-bottom: 0;
+}
+.bs-callout code {
+ border-radius: 3px;
+}
+.bs-callout + .bs-callout {
+ margin-top: -5px;
+}
+.bs-callout-default {
+ border-left-color: #777;
+}
+.bs-callout-default h4 {
+ color: #777;
+}
+.bs-callout-primary {
+ border-left-color: #428bca;
+}
+.bs-callout-primary h4 {
+ color: #428bca;
+}
+.bs-callout-success {
+ border-left-color: #5cb85c;
+}
+.bs-callout-success h4 {
+ color: #5cb85c;
+}
+.bs-callout-danger {
+ border-left-color: #d9534f;
+}
+.bs-callout-danger h4 {
+ color: #d9534f;
+}
+.bs-callout-warning {
+ border-left-color: #f0ad4e;
+}
+.bs-callout-warning h4 {
+ color: #f0ad4e;
+}
+.bs-callout-info {
+ border-left-color: #5bc0de;
+}
+.bs-callout-info h4 {
+ color: #5bc0de;
+}
+body {
+ background-color: #f9f9f9;
+ font-family: 'Source Sans Pro', sans-serif;
+}
+.card {
+ position: relative;
+ background: #ffffff;
+ margin-bottom: 20px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ width: 100%;
+ box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
+}
+.card:hover {
+ cursor: pointer;
+}
+.card.passed .header {
+ background-color: #00a65a;
+}
+.card.failed .header {
+ background-color: #f56956;
+}
+.card .header {
+ color: #fff;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+ border-bottom: 0px solid #f4f4f4;
+}
+.card .header:before,
+.card .header:after {
+ display: table;
+ content: " ";
+}
+.card .header h2 {
+ display: inline-block;
+ padding: 10px 10px 10px 10px;
+ margin: 0;
+ font-size: 20px;
+ font-weight: 400;
+ cursor: default;
+}
+.card .info {
+ padding: 10px;
+ height: 130px;
+ overflow: auto;
+}
+.card .footer {
+ border-top: 1px solid #f4f4f4;
+ -webkit-border-top-left-radius: 0;
+ -webkit-border-top-right-radius: 0;
+ -webkit-border-bottom-right-radius: 3px;
+ -webkit-border-bottom-left-radius: 3px;
+ -moz-border-radius-topleft: 0;
+ -moz-border-radius-topright: 0;
+ -moz-border-radius-bottomright: 3px;
+ -moz-border-radius-bottomleft: 3px;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px;
+ padding: 10px;
+ background-color: #ffffff;
+}
+.card .footer .progress {
+ margin-bottom: 0px;
+}
+.progress {
+ height: 20px;
+ overflow: hidden;
+ background-color: #f5f5f5;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+}
+.progress .progress-bar-green {
+ background-color: #00a65a !important;
+ background-image: none !important;
+}
+.progress .progress-bar-red {
+ background-color: #f56954 !important;
+ background-image: none !important;
+}
+#scenario-overview {
+ display: none;
+}
+#scenario-overview .feature {
+ display: none;
+}
+#scenario-overview .feature.selected {
+ display: block;
+}
+#scenario-overview .feature .details .panel.passed > .panel-heading {
+ color: white;
+ background-color: #00a65a;
+ background-image: none;
+}
+#scenario-overview .feature .details .panel.pending > .panel-heading {
+ color: white;
+ background-color: #e38d13;
+ background-image: none;
+}
+#scenario-overview .feature .details .panel.failed > .panel-heading {
+ color: white;
+ background-color: #f56956;
+ background-image: none;
+}
+.list-group-item.break {
+ padding: 1px;
+ background-color: #808080;
+}
\ No newline at end of file
diff --git a/reports/assets/Twig/css/style.less b/reports/assets/Twig/css/style.less
new file mode 100644
index 00000000..ab5b59dd
--- /dev/null
+++ b/reports/assets/Twig/css/style.less
@@ -0,0 +1,148 @@
+@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700);
+@import "callout.less";
+
+@color-failed: #F56956;
+@color-passed: #00a65a;
+@color-pending: #e38d13;
+
+body {
+ background-color: #f9f9f9;
+ font-family: 'Source Sans Pro', sans-serif;
+}
+
+.card {
+ position: relative;
+ background: #ffffff;
+ margin-bottom: 20px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ width: 100%;
+ box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ &.passed {
+ .header {
+ background-color: @color-passed;
+ }
+ }
+
+ &.failed {
+ .header {
+ background-color: @color-failed;
+ }
+ }
+
+ .header {
+ color: #fff;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+ border-bottom: 0px solid #f4f4f4;
+
+ &:before, &:after {
+ display: table;
+ content: " ";
+ }
+ h2 {
+ display: inline-block;
+ padding: 10px 10px 10px 10px;
+ margin: 0;
+ font-size: 20px;
+ font-weight: 400;
+ cursor: default;
+ }
+ }
+
+ .info {
+ padding: 10px;
+ height: 130px;
+ overflow: auto;
+ }
+
+ .footer {
+ border-top: 1px solid #f4f4f4;
+ -webkit-border-top-left-radius: 0;
+ -webkit-border-top-right-radius: 0;
+ -webkit-border-bottom-right-radius: 3px;
+ -webkit-border-bottom-left-radius: 3px;
+ -moz-border-radius-topleft: 0;
+ -moz-border-radius-topright: 0;
+ -moz-border-radius-bottomright: 3px;
+ -moz-border-radius-bottomleft: 3px;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px;
+ padding: 10px;
+ background-color: #ffffff;
+
+ .progress {
+ margin-bottom: 0px;
+ }
+ }
+
+}
+
+.progress {
+ height: 20px;
+ overflow: hidden;
+ background-color: #f5f5f5;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
+
+ .progress-bar-green {
+ background-color: @color-passed !important;
+ background-image: none !important;
+ }
+
+ .progress-bar-red {
+ background-color: #f56954 !important;
+ background-image: none !important;
+ }
+}
+
+#feature-overview {
+}
+
+#scenario-overview {
+ display: none;
+
+ .feature {
+ display: none;
+ &.selected {
+ display: block;
+ }
+
+ .details {
+ .panel {
+ &.passed {
+ > .panel-heading {
+ color: white;
+ background-color: @color-passed;
+ background-image: none;
+ }
+ }
+ &.pending {
+ > .panel-heading {
+ color: white;
+ background-color: @color-pending;
+ background-image: none;
+ }
+ }
+ &.failed {
+ > .panel-heading {
+ color: white;
+ background-color: @color-failed;
+ background-image: none;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/reports/index.html.html b/reports/index.html.html
new file mode 100644
index 00000000..6cfe092d
--- /dev/null
+++ b/reports/index.html.html
@@ -0,0 +1,1397 @@
+
+
+
+
+
+
+ Behat Tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3 Features:
+
+
0 failed
+
+
+
+
+
+
+ 27
+ Scenarios:
+
+
0 failed
+
0 undefined
+
0 skipped
+
+
+
+
+
+
+ 133
+ Steps:
+
+
0 failed
+
0 undefined
+
0 skipped
+
+
+
+
+
+
+
+
+
Suite: default
+
+
+
+
+
+
+
Como administrador del sistema
+Quiero gestionar productos y usuarios
+Para mantener la tienda actualizada
+
+
+
+
+
+
+
+
+
Como usuario del sistema
+Quiero poder registrarme y acceder a mi cuenta
+Para gestionar mis compras y datos personales
+
+
+
+
+
+
+
+
+
Como usuario del sistema
+Quiero gestionar mis compras y perfil
+Para tener una experiencia de compra satisfactoria
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Como administrador del sistema
+Quiero gestionar productos y usuarios
+Para mantener la tienda actualizada
+
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como administrador
+
+
+ Y estoy en el panel de administración
+
+
+ Entonces debería ver estadísticas de:
+
+ | Nuevos usuarios |
+
+ | Productos activos |
+
+ | Pedidos pendientes |
+
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como administrador
+
+
+ Y estoy en el panel de administración
+
+
+ Cuando selecciono un producto existente
+
+
+ Y modifico el precio a "149.99"
+
+
+ Y presiono "Actualizar"
+
+
+ Entonces debería ver "Producto actualizado correctamente"
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como administrador
+
+
+ Y estoy en el panel de administración
+
+
+ Cuando selecciono un producto existente
+
+
+ Y presiono "Eliminar"
+
+
+ Y confirmo la acción
+
+
+ Entonces debería ver "Producto eliminado correctamente"
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como administrador
+
+
+ Y estoy en el panel de administración
+
+
+ Cuando accedo a "usuarios"
+
+
+ Entonces debería ver la lista de usuarios registrados
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como administrador
+
+
+ Y estoy en el panel de administración
+
+
+ Cuando accedo a "pedidos"
+
+
+ Entonces debería ver los últimos pedidos
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como administrador
+
+
+ Y estoy en el panel de administración
+
+
+ Cuando accedo a "pedidos"
+
+
+ Y selecciono un pedido
+
+
+ Y cambio el estado a "Enviado"
+
+
+
+
+
+
+
+
+
+
+
+
Como usuario del sistema
+Quiero poder registrarme y acceder a mi cuenta
+Para gestionar mis compras y datos personales
+
+
+
+
+
+
+
+
+
+
+ Dado que estoy en la página de registro
+
+
+ Cuando completo el formulario con:
+
+ | email | juan@email.com |
+
+ | contraseña | password123 |
+
+ | confirmar | password123 |
+
+
+ Y presiono "Registrar"
+
+
+ Entonces debería ver "Registro exitoso"
+
+
+
+
+
+
+
+
+
+ Dado que estoy en la página de login
+
+
+ Cuando ingreso "usuario@test.com" como email
+
+
+ Y ingreso "password123" como contraseña
+
+
+ Y presiono "Iniciar sesión"
+
+
+ Entonces debería estar logueado
+
+
+ Y debería ver "Bienvenido"
+
+
+
+
+
+
+
+
+
+ Dado que estoy en la página de login
+
+
+ Cuando ingreso "usuario@test.com" como email
+
+
+ Y ingreso "wrongpassword" como contraseña
+
+
+ Y presiono "Iniciar sesión"
+
+
+ Entonces debería ver "Credenciales inválidas"
+
+
+
+
+
+
+
+
+
+ Dado que estoy en la página de recuperación
+
+
+ Cuando ingreso "usuario@test.com" como email
+
+
+ Y presiono "Recuperar contraseña"
+
+
+ Entonces debería ver "Email de recuperación enviado"
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado
+
+
+ Cuando presiono "Cerrar sesión"
+
+
+ Entonces debería estar deslogueado
+
+
+ Y debería ver "Sesión cerrada correctamente"
+
+
+
+
+
+
+
+
+
+
+
+
Como usuario del sistema
+Quiero gestionar mis compras y perfil
+Para tener una experiencia de compra satisfactoria
+
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como usuario
+
+
+ Y estoy en la página principal
+
+
+ Cuando accedo a "home"
+
+
+ Entonces debería ver la sección de últimos productos
+
+
+ Y debería ver la sección "Sobre nosotros"
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como usuario
+
+
+ Y estoy en la página principal
+
+
+ Cuando accedo a "search_page"
+
+
+ Y busco el término "película"
+
+
+ Entonces debería ver productos relacionados con "película"
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como usuario
+
+
+ Y estoy en la página principal
+
+
+ Cuando busco el término "xyzabc123"
+
+
+ Entonces debería ver el mensaje "¡No se han encontrado resultados!"
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como usuario
+
+
+ Y estoy en la página principal
+
+
+ Cuando accedo a "shop"
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como usuario
+
+
+ Y estoy en la página principal
+
+
+ Dado que estoy en la tienda
+
+
+ Cuando selecciono un producto
+
+
+ Y establezco cantidad "2"
+
+
+ Y presiono "Añadir al carrito"
+
+
+ Entonces debería ver el mensaje "Producto agregado al carrito"
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como usuario
+
+
+ Y estoy en la página principal
+
+
+ Dado que tengo productos en el carrito
+
+
+ Cuando accedo al checkout
+
+
+ Y completo los datos de envío:
+
+ | email | juan@email.com |
+
+ | teléfono | 987654321 |
+
+ | dirección | Calle 123 |
+
+
+ Y selecciono método de pago "Pago en persona"
+
+
+ Entonces debería poder finalizar la compra
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como usuario
+
+
+ Y estoy en la página principal
+
+
+ Cuando accedo a "orders"
+
+
+ Entonces debería ver mis pedidos anteriores
+
+
+ Y cada pedido debería mostrar:
+
+ | Total |
+
+ | Estado |
+
+ | Método de pago |
+
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como usuario
+
+
+ Y estoy en la página principal
+
+
+ Cuando accedo al checkout
+
+
+ Y no tengo productos en el carrito
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como usuario
+
+
+ Y estoy en la página principal
+
+
+ Dado que tengo productos en el carrito
+
+
+ Cuando actualizo la cantidad de un producto
+
+
+ Entonces el total debería actualizarse
+
+
+ Y debería ver el nuevo subtotal
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como usuario
+
+
+ Y estoy en la página principal
+
+
+ Dado que tengo productos en el carrito
+
+
+ Cuando elimino un producto
+
+
+ Entonces debería ver el mensaje "Producto eliminado del carrito"
+
+
+ Y el total debería actualizarse
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como usuario
+
+
+ Y estoy en la página principal
+
+
+ Cuando intento hacer checkout
+
+
+ Y no completo todos los campos requeridos
+
+
+ Entonces debería ver mensajes de validación
+
+
+ Y no debería poder continuar
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como usuario
+
+
+ Y estoy en la página principal
+
+
+ Cuando selecciono un producto específico
+
+
+ Entonces debería ver:
+
+ | Descripción |
+
+ | Precio |
+
+ | Disponibilidad |
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como usuario
+
+
+ Y estoy en la página principal
+
+
+ Cuando accedo a la tienda
+
+
+ Y aplico filtros:
+
+ | Precio | 0-100 |
+
+
+ Entonces debería ver solo productos que cumplan los criterios
+
+
+
+
+
+
+
+
+
+ Dado que estoy logueado como usuario
+
+
+ Y estoy en la página principal
+
+
+ Cuando veo un producto
+
+
+ Y presiono "Compartir"
+
+
+ Entonces debería poder compartir en redes sociales:
+
+ | Twitter |
+
+ | WhatsApp |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sonar-project.properties b/sonar-project.properties
new file mode 100644
index 00000000..72e1a24b
--- /dev/null
+++ b/sonar-project.properties
@@ -0,0 +1,12 @@
+sonar.organization=pyfanpage
+sonar.projectKey=JosueUPT_CalidadU2
+
+sonar.sources=src
+sonar.tests=tests
+sonar.language=php
+sonar.sourceEncoding=UTF-8
+sonar.php.file.suffixes=php
+
+sonar.php.coverage.reportPaths=coverage.xml
+
+sonar.coverage.exclusions=tests/**, src/Views/**/*, src/autoload.php, src/Config/**
diff --git a/sql/tienda_bd.sql b/sql/tienda_bd.sql
new file mode 100644
index 00000000..cf10bf2e
--- /dev/null
+++ b/sql/tienda_bd.sql
@@ -0,0 +1,201 @@
+tienda_bd-- phpMyAdmin SQL Dump
+-- version 5.2.1
+-- https://www.phpmyadmin.net/
+--
+-- Host: 127.0.0.1
+-- Generation Time: Aug 30, 2024 at 06:20 AM
+-- Server version: 10.4.32-MariaDB
+-- PHP Version: 8.2.12
+
+SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
+START TRANSACTION;
+SET time_zone = "+00:00";
+
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8mb4 */;
+
+--
+-- Database: `tienda_bd`
+--
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `cart`
+--
+
+CREATE TABLE `cart` (
+ `id` int(100) NOT NULL,
+ `user_id` int(100) NOT NULL,
+ `name` varchar(100) NOT NULL,
+ `price` int(100) NOT NULL,
+ `quantity` int(100) NOT NULL,
+ `image` varchar(100) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `message`
+--
+
+CREATE TABLE `message` (
+ `id` int(100) NOT NULL,
+ `user_id` int(100) NOT NULL,
+ `name` varchar(100) NOT NULL,
+ `email` varchar(100) NOT NULL,
+ `number` varchar(12) NOT NULL,
+ `message` varchar(500) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+--
+-- Dumping data for table `message`
+--
+
+INSERT INTO `message` (`id`, `user_id`, `name`, `email`, `number`, `message`) VALUES
+(10, 1, 'test', 'test@hotmail.com', '123456', 'teste');
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `orders`
+--
+
+CREATE TABLE `orders` (
+ `id` int(100) NOT NULL,
+ `user_id` int(100) NOT NULL,
+ `name` varchar(100) NOT NULL,
+ `number` varchar(12) NOT NULL,
+ `email` varchar(100) NOT NULL,
+ `method` varchar(50) NOT NULL,
+ `address` varchar(500) NOT NULL,
+ `total_products` varchar(1000) NOT NULL,
+ `total_price` int(100) NOT NULL,
+ `placed_on` varchar(50) NOT NULL,
+ `payment_status` varchar(20) NOT NULL DEFAULT 'pendiente'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+--
+-- Dumping data for table `orders`
+--
+
+INSERT INTO `orders` (`id`, `user_id`, `name`, `number`, `email`, `method`, `address`, `total_products`, `total_price`, `placed_on`, `payment_status`) VALUES
+(10, 1, 'test', '123456', 'test@hotmail.com', 'cash on delivery', 'flat no. 123, afsfdsfs, fafdsa, fdafda - 1233', ', Test (3) ', 75, '30-Aug-2024', 'completado');
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `products`
+--
+
+CREATE TABLE `products` (
+ `id` int(100) NOT NULL,
+ `name` varchar(100) NOT NULL,
+ `price` int(100) NOT NULL,
+ `image` varchar(100) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+--
+-- Dumping data for table `products`
+--
+
+INSERT INTO `products` (`id`, `name`, `price`, `image`) VALUES
+(1, 'Test', 25, 'author-2.jpg');
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `users`
+--
+
+CREATE TABLE `users` (
+ `id` int(100) NOT NULL,
+ `name` varchar(100) NOT NULL,
+ `email` varchar(100) NOT NULL,
+ `password` varchar(100) NOT NULL,
+ `user_type` varchar(20) NOT NULL DEFAULT 'user'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+--
+-- Dumping data for table `users`
+--
+
+INSERT INTO `users` (`id`, `name`, `email`, `password`, `user_type`) VALUES
+(1, 'test', 'test@hotmail.com', '$2y$12$QhjD2tOhCFANCGUX7Nvm5.WNcJYMSE/7os5j72PgracKfoWseRejy', 'user'),
+(2, 'admin', 'admin@hotmail.com', '$2y$12$QhjD2tOhCFANCGUX7Nvm5.WNcJYMSE/7os5j72PgracKfoWseRejy', 'admin');
+
+--
+-- Indexes for dumped tables
+--
+
+--
+-- Indexes for table `cart`
+--
+ALTER TABLE `cart`
+ ADD PRIMARY KEY (`id`);
+
+--
+-- Indexes for table `message`
+--
+ALTER TABLE `message`
+ ADD PRIMARY KEY (`id`);
+
+--
+-- Indexes for table `orders`
+--
+ALTER TABLE `orders`
+ ADD PRIMARY KEY (`id`);
+
+--
+-- Indexes for table `products`
+--
+ALTER TABLE `products`
+ ADD PRIMARY KEY (`id`);
+
+--
+-- Indexes for table `users`
+--
+ALTER TABLE `users`
+ ADD PRIMARY KEY (`id`);
+
+--
+-- AUTO_INCREMENT for dumped tables
+--
+
+--
+-- AUTO_INCREMENT for table `cart`
+--
+ALTER TABLE `cart`
+ MODIFY `id` int(100) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=58;
+
+--
+-- AUTO_INCREMENT for table `message`
+--
+ALTER TABLE `message`
+ MODIFY `id` int(100) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=11;
+
+--
+-- AUTO_INCREMENT for table `orders`
+--
+ALTER TABLE `orders`
+ MODIFY `id` int(100) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=11;
+
+--
+-- AUTO_INCREMENT for table `products`
+--
+ALTER TABLE `products`
+ MODIFY `id` int(100) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
+
+--
+-- AUTO_INCREMENT for table `users`
+--
+ALTER TABLE `users`
+ MODIFY `id` int(100) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
+COMMIT;
+
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
diff --git a/src/.phpunit.result.cache b/src/.phpunit.result.cache
new file mode 100644
index 00000000..65bc1902
--- /dev/null
+++ b/src/.phpunit.result.cache
@@ -0,0 +1 @@
+{"version":1,"defects":[],"times":{"LoginTest::testExample":0.004}}
\ No newline at end of file
diff --git a/src/Config/Database.php b/src/Config/Database.php
new file mode 100644
index 00000000..d379666c
--- /dev/null
+++ b/src/Config/Database.php
@@ -0,0 +1,70 @@
+setDefaultCredentials();
+ return;
+ }
+
+ $envPath = __DIR__ . '/../../.env';
+
+ if (file_exists($envPath)) {
+ $env = parse_ini_file($envPath);
+ if ($env !== false) {
+ $this->host = $env['DB_HOST'];
+ $this->user = $env['DB_USER'];
+ $this->password = $env['DB_PASSWORD'];
+ $this->database = $env['DB_NAME'];
+ return;
+ }
+ }
+
+ $this->setDefaultCredentials();
+ }
+
+ private function setDefaultCredentials(): void
+ {
+ $this->host = getenv('DB_HOST') ?: 'db';
+ $this->user = getenv('DB_USER') ?: 'root';
+ $this->password = getenv('DB_PASSWORD') ?: '123456';
+ $this->database = getenv('DB_NAME') ?: 'tienda_bd';
+ }
+
+ public function setCredentials(string $host, string $user, string $password, string $database): void
+ {
+ $this->host = $host;
+ $this->user = $user;
+ $this->password = $password;
+ $this->database = $database;
+ }
+
+ public function connect() {
+ try {
+ $conn = new \PDO(
+ "mysql:host={$this->host};dbname={$this->database}",
+ $this->user,
+ $this->password,
+ array(\PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")
+ );
+ $conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+ return $conn;
+ } catch(\PDOException $e) {
+ error_log("Error de conexión: " . $e->getMessage());
+ throw new DatabaseException(
+ "Error de conexión a la base de datos: " . $e->getMessage(),
+ $e->getCode(),
+ $e
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Controllers/AdminController.php b/src/Controllers/AdminController.php
new file mode 100644
index 00000000..4299ca4b
--- /dev/null
+++ b/src/Controllers/AdminController.php
@@ -0,0 +1,407 @@
+conn = $conn;
+ }
+
+ public function getDashboardData() {
+ $data = [];
+
+ // Obtener total pendientes
+ $data['total_pendings'] = $this->getTotalPendings();
+ $data['total_completed'] = $this->getTotalCompleted();
+ $data['orders_count'] = $this->getOrdersCount();
+ $data['products_count'] = $this->getProductsCount();
+ $data['users_count'] = $this->getUsersCount();
+ $data['admins_count'] = $this->getAdminsCount();
+ $data['total_accounts'] = $this->getTotalAccounts();
+ $data['messages_count'] = $this->getMessagesCount();
+
+ return $data;
+ }
+
+ private function getTotalPendings() {
+ $stmt = $this->conn->prepare("SELECT * FROM `orders` WHERE payment_status = 'pendiente'");
+ $stmt->execute();
+ $total = 0;
+ foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $order = new Order();
+ $order->setId($row['id']);
+ $order->setTotalPrice($row['total_price']);
+ $total += $order->getTotalPrice();
+ }
+ return $total;
+ }
+
+ private function getTotalCompleted() {
+ $stmt = $this->conn->prepare("SELECT * FROM `orders` WHERE payment_status = 'completado'");
+ $stmt->execute();
+ $total = 0;
+ foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $order = new Order();
+ $order->setId($row['id']);
+ $order->setTotalPrice($row['total_price']);
+ $total += $order->getTotalPrice();
+ }
+ return $total;
+ }
+
+ private function getOrdersCount() {
+ $query = "SELECT COUNT(*) as count FROM `orders`";
+ $stmt = $this->conn->prepare($query);
+ $stmt->execute();
+ return $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
+ }
+
+ private function getProductsCount() {
+ $query = "SELECT COUNT(*) as count FROM `products`";
+ $stmt = $this->conn->prepare($query);
+ $stmt->execute();
+ return $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
+ }
+
+ private function getUsersCount() {
+ $query = "SELECT COUNT(*) as count FROM `users` WHERE user_type = 'user'";
+ $stmt = $this->conn->prepare($query);
+ $stmt->execute();
+ return $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
+ }
+
+ private function getAdminsCount() {
+ $query = "SELECT COUNT(*) as count FROM `users` WHERE user_type = 'admin'";
+ $stmt = $this->conn->prepare($query);
+ $stmt->execute();
+ return $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
+ }
+
+ private function getTotalAccounts() {
+ $query = "SELECT COUNT(*) as count FROM `users`";
+ $stmt = $this->conn->prepare($query);
+ $stmt->execute();
+ return $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
+ }
+
+ private function getMessagesCount() {
+ $query = "SELECT COUNT(*) as count FROM `message`";
+ $stmt = $this->conn->prepare($query);
+ $stmt->execute();
+ return $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
+ }
+
+ private function handleDatabaseError($e) {
+ error_log("Error en la base de datos: " . $e->getMessage());
+ throw new \Exception("Error al procesar la solicitud");
+ }
+
+ public function addProduct($postData, $files) {
+ try {
+ $product = new Product();
+ $product->setName($postData['name']);
+ $product->setPrice($postData['price']);
+ $product->setImage($files['image']['name']);
+
+ // Verificar si el producto ya existe
+ $stmt = $this->conn->prepare("SELECT name FROM `products` WHERE name = ?");
+ $stmt->execute([$product->getName()]);
+
+ if($stmt->rowCount() > 0) {
+ return ['success' => false, 'message' => 'El producto ya existe'];
+ }
+
+ if($files['image']['size'] > 2000000) {
+ return ['success' => false, 'message' => 'El tamaño de la imagen es demasiado grande'];
+ }
+
+ $stmt = $this->conn->prepare("INSERT INTO `products`(name, price, image) VALUES(?, ?, ?)");
+ if($stmt->execute([$product->getName(), $product->getPrice(), $product->getImage()])) {
+ move_uploaded_file($files['image']['tmp_name'], self::UPLOAD_PATH . $product->getImage());
+ return ['success' => true, 'message' => '¡Producto añadido exitosamente!'];
+ }
+ } catch (\Exception $e) {
+ $this->handleDatabaseError($e);
+ return ['success' => false, 'message' => 'Error al añadir el producto'];
+ }
+ }
+
+ public function deleteProduct($id) {
+ try {
+ // Obtener información de la imagen
+ $stmt = $this->conn->prepare("SELECT image FROM `products` WHERE id = ?");
+ $stmt->execute([$id]);
+ $image_data = $stmt->fetch(\PDO::FETCH_ASSOC);
+
+ if($image_data) {
+ $imagePath = self::UPLOAD_PATH . $image_data['image'];
+ if(file_exists($imagePath)) {
+ unlink($imagePath);
+ }
+ }
+
+ $stmt = $this->conn->prepare("DELETE FROM `products` WHERE id = ?");
+ if($stmt->execute([$id])) {
+ return ['success' => true, 'message' => 'Producto eliminado'];
+ }
+
+ return ['success' => false, 'message' => 'Error al eliminar el producto'];
+ } catch (\Exception $e) {
+ return ['success' => false, 'message' => 'Error al eliminar el producto: ' . $e->getMessage()];
+ }
+ }
+
+ private function validateImageName($imageName) {
+ // Solo permitir letras, números, guiones y puntos
+ return preg_match('/^[a-zA-Z0-9_.-]+$/', $imageName);
+ }
+
+ private function getSecureImagePath($imageName) {
+ try {
+ // Validar el nombre del archivo
+ if (empty($imageName) || !is_string($imageName)) {
+ throw new \Exception('Nombre de archivo inválido');
+ }
+
+ // Obtener y validar la extensión
+ $extension = strtolower(pathinfo($imageName, PATHINFO_EXTENSION));
+ if (!in_array($extension, self::ALLOWED_EXTENSIONS)) {
+ throw new \Exception('Tipo de archivo no permitido');
+ }
+
+ // Verificar que el archivo existe en la base de datos
+ $stmt = $this->conn->prepare("SELECT image FROM products WHERE image = ?");
+ $stmt->execute([$imageName]);
+ if (!$stmt->fetch()) {
+ throw new \Exception('Archivo no encontrado en la base de datos');
+ }
+
+ return self::UPLOAD_PATH . $imageName;
+ } catch (\Exception $e) {
+ error_log("Error en getSecureImagePath: " . $e->getMessage());
+ return false;
+ }
+ }
+
+ private function handleImageDelete($imageName) {
+ try {
+ // Validar el nombre del archivo
+ if (empty($imageName) || !is_string($imageName)) {
+ return false;
+ }
+
+ // Verificar en la base de datos
+ $stmt = $this->conn->prepare("SELECT image FROM products WHERE image = ? LIMIT 1");
+ $stmt->execute([$imageName]);
+ $result = $stmt->fetch(\PDO::FETCH_ASSOC);
+
+ if (!$result) {
+ return false;
+ }
+
+ // Construir y validar la ruta
+ $fullPath = realpath(self::UPLOAD_PATH . $result['image']);
+ $uploadDir = realpath(self::UPLOAD_PATH);
+
+ // Verificar que el archivo está dentro del directorio permitido
+ if ($fullPath === false || strpos($fullPath, $uploadDir) !== 0) {
+ return false;
+ }
+
+ // Eliminar el archivo si existe
+ if (file_exists($fullPath)) {
+ return unlink($fullPath);
+ }
+
+ return false;
+ } catch (\Exception $e) {
+ error_log("Error al eliminar imagen: " . $e->getMessage());
+ return false;
+ }
+ }
+
+ public function updateProduct($postData, $files) {
+ try {
+ $product = new Product();
+ $product->setId($postData['update_p_id']);
+ $product->setName($postData['update_name']);
+ $product->setPrice($postData['update_price']);
+
+ if(!empty($files['update_image']['name'])) {
+ if($files['update_image']['size'] > 2000000) {
+ return ['success' => false, 'message' => 'El tamaño de la imagen es demasiado grande'];
+ }
+
+ // Eliminar imagen anterior de forma segura
+ if (!empty($postData['update_old_image'])) {
+ $this->handleImageDelete($postData['update_old_image']);
+ }
+
+ // Generar nombre único para la nueva imagen
+ $extension = strtolower(pathinfo($files['update_image']['name'], PATHINFO_EXTENSION));
+ $newImageName = uniqid() . '.' . $extension;
+ $product->setImage($newImageName);
+
+ // Subir nueva imagen
+ move_uploaded_file(
+ $files['update_image']['tmp_name'],
+ self::UPLOAD_PATH . $newImageName
+ );
+
+ $stmt = $this->conn->prepare("UPDATE `products` SET name = ?, price = ?, image = ? WHERE id = ?");
+ $params = [$product->getName(), $product->getPrice(), $product->getImage(), $product->getId()];
+ } else {
+ $stmt = $this->conn->prepare("UPDATE `products` SET name = ?, price = ? WHERE id = ?");
+ $params = [$product->getName(), $product->getPrice(), $product->getId()];
+ }
+
+ if($stmt->execute($params)) {
+ return ['success' => true, 'message' => 'Producto actualizado exitosamente'];
+ }
+
+ return ['success' => false, 'message' => 'Error al actualizar el producto'];
+ } catch (\Exception $e) {
+ error_log("Error en updateProduct: " . $e->getMessage());
+ return ['success' => false, 'message' => 'Error al actualizar el producto'];
+ }
+ }
+
+ public function getAllProducts() {
+ $stmt = $this->conn->query("SELECT * FROM `products`");
+ $products = [];
+ foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $product = new Product();
+ $product->setId($row['id']);
+ $product->setName($row['name']);
+ $product->setPrice($row['price']);
+ $product->setImage($row['image']);
+ $products[] = $product;
+ }
+ return $products;
+ }
+
+ public function getAllOrders() {
+ $stmt = $this->conn->prepare("SELECT * FROM `orders`");
+ $stmt->execute();
+ $orders = [];
+ foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $order = new Order();
+ $order->setId($row['id']);
+ $order->setUserId($row['user_id']);
+ $order->setPlacedOn($row['placed_on']);
+ $order->setName($row['name']);
+ $order->setNumber($row['number']);
+ $order->setEmail($row['email']);
+ $order->setAddress($row['address']);
+ $order->setTotalProducts($row['total_products']);
+ $order->setTotalPrice($row['total_price']);
+ $order->setMethod($row['method']);
+ $order->setPaymentStatus($row['payment_status']);
+ $orders[] = $order;
+ }
+ return $orders;
+ }
+
+ public function updateOrderStatus($orderId, $status) {
+ try {
+ $order = new Order();
+ $order->setId($orderId);
+ $order->setPaymentStatus($status);
+
+ $stmt = $this->conn->prepare("UPDATE `orders` SET payment_status = ? WHERE id = ?");
+ return $stmt->execute([$order->getPaymentStatus(), $order->getId()]);
+ } catch (\PDOException $e) {
+ $this->handleDatabaseError($e);
+ return false;
+ }
+ }
+
+ public function deleteOrder($orderId) {
+ try {
+ $order = new Order();
+ $order->setId($orderId);
+
+ $stmt = $this->conn->prepare("DELETE FROM `orders` WHERE id = ?");
+ return $stmt->execute([$order->getId()]);
+ } catch (\PDOException $e) {
+ $this->handleDatabaseError($e);
+ return false;
+ }
+ }
+
+ public function getAllUsers() {
+ try {
+ $stmt = $this->conn->prepare("SELECT * FROM `users`");
+ $stmt->execute();
+ $users = [];
+
+ foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $user = new User();
+ $user->setId($row['id']);
+ $user->setName($row['name']);
+ $user->setEmail($row['email']);
+ $user->setUserType($row['user_type']);
+ $users[] = $user;
+ }
+ return $users;
+ } catch (\PDOException $e) {
+ $this->handleDatabaseError($e);
+ return [];
+ }
+ }
+
+ public function deleteUser($userId) {
+ try {
+ $user = new User();
+ $user->setId($userId);
+
+ $stmt = $this->conn->prepare("DELETE FROM `users` WHERE id = ?");
+ return $stmt->execute([$user->getId()]);
+ } catch (\PDOException $e) {
+ $this->handleDatabaseError($e);
+ return false;
+ }
+ }
+
+ public function getAllMessages() {
+ try {
+ $stmt = $this->conn->query("SELECT * FROM `message`");
+ $messages = [];
+ foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $message = new Message();
+ $message->setId($row['id']);
+ $message->setUserId($row['user_id']);
+ $message->setMessage($row['message']);
+ $message->setName($row['name']);
+ $message->setEmail($row['email']);
+ $message->setNumber($row['number']);
+ $messages[] = $message;
+ }
+ return $messages;
+ } catch (\PDOException $e) {
+ $this->handleDatabaseError($e);
+ return [];
+ }
+ }
+
+ public function deleteMessage($messageId) {
+ try {
+ $query = "DELETE FROM `message` WHERE id = ?";
+ $stmt = $this->conn->prepare($query);
+ return $stmt->execute([$messageId]);
+ } catch (\PDOException $e) {
+ $this->handleDatabaseError($e);
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Controllers/ContactController.php b/src/Controllers/ContactController.php
new file mode 100644
index 00000000..18a8f253
--- /dev/null
+++ b/src/Controllers/ContactController.php
@@ -0,0 +1,65 @@
+conn = $conn;
+ }
+
+ // Agregar estos métodos protegidos para permitir el mock en las pruebas
+ protected function createUser()
+ {
+ return new User();
+ }
+
+ protected function createMessage()
+ {
+ return new Message();
+ }
+
+ public function sendMessage($userData) {
+ try {
+ // Validar que todos los campos requeridos existan
+ if (!isset($userData['user_id']) || !isset($userData['name']) ||
+ !isset($userData['email']) || !isset($userData['message'])) {
+ return ['success' => false, 'message' => 'Faltan campos requeridos'];
+ }
+
+ // Modificar la creación de User y Message
+ $user = $this->createUser();
+ $user->setId($userData['user_id']);
+ if (!$user->exists($this->conn)) {
+ return ['success' => false, 'message' => 'Usuario no encontrado'];
+ }
+
+ $message = $this->createMessage();
+ $message->setUserId($userData['user_id']);
+ $message->setName($userData['name']);
+ $message->setEmail($userData['email']);
+ $message->setNumber($userData['number']);
+ $message->setMessage($userData['message']);
+
+ if ($message->exists($this->conn)) { // Asumiendo que existe un método exists() en Message
+ return ['success' => false, 'message' => '¡Mensaje ya enviado!'];
+ }
+
+ if ($message->save($this->conn)) {
+ return ['success' => true, 'message' => '¡Mensaje enviado exitosamente!'];
+ }
+
+ // Añadir log específico si save() falla
+ error_log("Error en save(): Falló al guardar el mensaje para usuario ID: " . $userData['user_id']);
+ return ['success' => false, 'message' => 'Error al enviar mensaje'];
+ } catch (\Exception $e) {
+ error_log("Error al enviar mensaje: " . $e->getMessage());
+ return ['success' => false, 'message' => 'Error al enviar mensaje: ' . $e->getMessage()];
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Controllers/OrderController.php b/src/Controllers/OrderController.php
new file mode 100644
index 00000000..4795a48e
--- /dev/null
+++ b/src/Controllers/OrderController.php
@@ -0,0 +1,249 @@
+conn = $conn;
+ }
+
+ public function getOrders($userId) {
+ try {
+ $stmt = $this->conn->prepare("SELECT * FROM orders WHERE user_id = ?");
+ $stmt->execute([$userId]);
+ $orders = [];
+ foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $order = new Order();
+ $order->setId($row['id']);
+ $order->setUserId($row['user_id']);
+ $order->setName($row['name']);
+ $order->setNumber($row['number']);
+ $order->setEmail($row['email']);
+ $order->setMethod($row['method']);
+ $order->setAddress($row['address']);
+ $order->setTotalProducts($row['total_products']);
+ $order->setTotalPrice($row['total_price']);
+ $order->setPaymentStatus($row['payment_status']);
+ $order->setPlacedOn($row['placed_on']);
+ $orders[] = $order;
+ }
+ return $orders;
+ } catch (\Exception $e) {
+ $this->handleDatabaseError($e);
+ return [];
+ }
+ }
+
+ public function updatePaymentStatus($orderId, $status) {
+ try {
+ $stmt = $this->conn->prepare("UPDATE orders SET payment_status = ? WHERE id = ?");
+ return $stmt->execute([$status, $orderId]);
+ } catch (\Exception $e) {
+ error_log("Error al actualizar estado: " . $e->getMessage());
+ return false;
+ }
+ }
+
+ public function deleteOrder($orderId) {
+ try {
+ $stmt = $this->conn->prepare("DELETE FROM orders WHERE id = ?");
+ return $stmt->execute([$orderId]);
+ } catch (\Exception $e) {
+ error_log("Error al eliminar orden: " . $e->getMessage());
+ return false;
+ }
+ }
+
+ public function getAllOrders() {
+ try {
+ $stmt = $this->conn->prepare("SELECT * FROM `orders`");
+ $stmt->execute();
+ $orders = [];
+ foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $order = new Order();
+ $order->setUserId($row['user_id']);
+ $order->setName($row['name']);
+ $order->setEmail($row['email']);
+ $order->setMethod($row['method']);
+ $order->setAddress($row['address']);
+ $order->setTotalProducts($row['total_products']);
+ $order->setTotalPrice($row['total_price']);
+ $orders[] = $order;
+ }
+ return $orders;
+ } catch (\Exception $e) {
+ $this->handleDatabaseError($e);
+ return [];
+ }
+ }
+
+ public function createOrder($userData, $userId) {
+ try {
+ $order = new Order();
+ $order->setUserId($userId);
+ $order->setName($userData['name']);
+ $order->setNumber($userData['number']);
+ $order->setEmail($userData['email']);
+ $order->setMethod($userData['method']);
+
+ // Obtener productos del carrito
+ $cartItems = $this->getCartItems($userId);
+ if(empty($cartItems)) {
+ return ['success' => false, 'message' => 'El carrito está vacío'];
+ }
+
+ // Calcular total y preparar lista de productos
+ $cartTotal = 0;
+ $products = [];
+ foreach($cartItems as $item) {
+ $products[] = $item['name'] . ' (' . $item['quantity'] . ')';
+ $cartTotal += ($item['price'] * $item['quantity']);
+ }
+ $totalProducts = implode(', ', $products);
+
+ // Formatear y establecer dirección
+ $address = 'flat no. ' . $userData['flat'] . ', ' .
+ $userData['street'] . ', ' .
+ $userData['city'] . ', ' .
+ $userData['country'] . ' - ' .
+ $userData['pin_code'];
+
+ // Establecer valores adicionales en el objeto Order
+ $order->setAddress($address);
+ $order->setTotalProducts($totalProducts);
+ $order->setTotalPrice($cartTotal);
+
+ // Verificar si la orden ya existe
+ $stmt = $this->conn->prepare("SELECT * FROM orders WHERE
+ name = ? AND number = ? AND email = ? AND
+ method = ? AND address = ? AND
+ total_products = ? AND total_price = ?");
+
+ $stmt->execute([
+ $order->getName(),
+ $order->getNumber(),
+ $order->getEmail(),
+ $order->getMethod(),
+ $order->getAddress(),
+ $order->getTotalProducts(),
+ $order->getTotalPrice()
+ ]);
+
+ if($stmt->rowCount() > 0) {
+ return ['success' => false, 'message' => '¡Pedido ya realizado!'];
+ }
+
+ // Insertar nueva orden
+ $stmt = $this->conn->prepare("INSERT INTO orders
+ (user_id, name, number, email, method, address,
+ total_products, total_price, placed_on)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
+
+ $stmt->execute([
+ $order->getUserId(),
+ $order->getName(),
+ $order->getNumber(),
+ $order->getEmail(),
+ $order->getMethod(),
+ $order->getAddress(),
+ $order->getTotalProducts(),
+ $order->getTotalPrice(),
+ date('d-M-Y')
+ ]);
+
+ // Limpiar carrito
+ $this->clearCart($userId);
+
+ return ['success' => true, 'message' => '¡Pedido realizado con éxito!'];
+ } catch (\Exception $e) {
+ error_log("Error al crear orden: " . $e->getMessage());
+ return ['success' => false, 'message' => 'Error al procesar el pedido'];
+ }
+ }
+
+ private function getCartItems($userId) {
+ $stmt = $this->conn->prepare("SELECT * FROM cart WHERE user_id = ?");
+ $stmt->execute([$userId]);
+ return $stmt->fetchAll(\PDO::FETCH_ASSOC);
+ }
+
+ private function clearCart($userId) {
+ $stmt = $this->conn->prepare("DELETE FROM cart WHERE user_id = ?");
+ $stmt->execute([$userId]);
+ }
+
+ public function getUserOrders($userId) {
+ try {
+ $stmt = $this->conn->prepare("SELECT * FROM orders WHERE user_id = ?");
+ $stmt->execute([$userId]);
+ $orders = [];
+ foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $order = new Order();
+ $order->setUserId($row['user_id']);
+ $order->setName($row['name']);
+ $order->setNumber($row['number']);
+ $order->setEmail($row['email']);
+ $order->setMethod($row['method']);
+ $order->setAddress($row['address']);
+ $order->setTotalProducts($row['total_products']);
+ $order->setTotalPrice($row['total_price']);
+ $order->setPaymentStatus($row['payment_status']);
+ $order->setPlacedOn($row['placed_on']);
+ $orders[] = $order;
+ }
+ return $orders;
+ } catch (\Exception $e) {
+ error_log("Error al obtener órdenes del usuario: " . $e->getMessage());
+ return [];
+ }
+ }
+
+ public function getAllProducts() {
+ try {
+ $stmt = $this->conn->query("SELECT * FROM `products`");
+ $products = [];
+ foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $product = new Product();
+ $product->setName($row['name']);
+ $product->setPrice($row['price']);
+ $product->setImage($row['image']);
+ $products[] = $product;
+ }
+ return $products;
+ } catch (\Exception $e) {
+ $this->handleDatabaseError($e);
+ return [];
+ }
+ }
+
+ public function getAllUsers() {
+ try {
+ $stmt = $this->conn->prepare("SELECT * FROM `users`");
+ $stmt->execute();
+ $users = [];
+ foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $user = new User();
+ $user->setName($row['name']);
+ $user->setEmail($row['email']);
+ $user->setUserType($row['user_type']);
+ $users[] = $user;
+ }
+ return $users;
+ } catch (\Exception $e) {
+ $this->handleDatabaseError($e);
+ return [];
+ }
+ }
+
+ private function handleDatabaseError(\Exception $e) {
+ error_log("Error en la base de datos: " . $e->getMessage());
+ }
+}
\ No newline at end of file
diff --git a/src/Controllers/ProductController.php b/src/Controllers/ProductController.php
new file mode 100644
index 00000000..40cc7b84
--- /dev/null
+++ b/src/Controllers/ProductController.php
@@ -0,0 +1,146 @@
+conn = $conn;
+ }
+
+ public function getLatestProducts($limit = 6) {
+ try {
+ $query = "SELECT * FROM products LIMIT :limit";
+ $stmt = $this->conn->prepare($query);
+ $stmt->bindValue(':limit', $limit, \PDO::PARAM_INT);
+ $stmt->execute();
+
+ $products = [];
+ foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $product = new Product();
+ $product->setId($row['id']);
+ $product->setName($row['name']);
+ $product->setPrice($row['price']);
+ $product->setImage($row['image']);
+ $products[] = $product;
+ }
+ return $products;
+ } catch (\Exception $e) {
+ error_log("Error al obtener productos: " . $e->getMessage());
+ return [];
+ }
+ }
+
+ public function addToCart($userId, $productData) {
+ try {
+ $cart = new Cart();
+ $cart->setUserId($userId);
+ $cart->setName($productData['product_name']);
+ $cart->setPrice($productData['product_price']);
+ $cart->setQuantity($productData['product_quantity']);
+ $cart->setImage($productData['product_image']);
+
+ // Verificar si el producto ya está en el carrito
+ $stmt = $this->conn->prepare("SELECT * FROM cart WHERE user_id = ? AND name = ?");
+ $stmt->execute([$userId, $cart->getName()]);
+
+ if($stmt->rowCount() > 0) {
+ return ['success' => false, 'message' => 'El producto ya está en el carrito'];
+ }
+
+ // Añadir al carrito
+ $query = "INSERT INTO cart (user_id, name, price, quantity, image)
+ VALUES (?, ?, ?, ?, ?)";
+ $stmt = $this->conn->prepare($query);
+
+ $stmt->execute([
+ $cart->getUserId(),
+ $cart->getName(),
+ $cart->getPrice(),
+ $cart->getQuantity(),
+ $cart->getImage()
+ ]);
+
+ return ['success' => true, 'message' => 'Producto añadido al carrito'];
+ } catch (\Exception $e) {
+ error_log("Error al añadir al carrito: " . $e->getMessage());
+ return ['success' => false, 'message' => 'Error al añadir al carrito'];
+ }
+ }
+
+ public function getAllProducts() {
+ try {
+ $query = "SELECT * FROM products";
+ $stmt = $this->conn->prepare($query);
+ $stmt->execute();
+
+ $products = [];
+ foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $product = new Product();
+ $product->setId($row['id']);
+ $product->setName($row['name']);
+ $product->setPrice($row['price']);
+ $product->setImage($row['image']);
+ $products[] = $product;
+ }
+ return $products;
+ } catch (\Exception $e) {
+ error_log("Error al obtener todos los productos: " . $e->getMessage());
+ return [];
+ }
+ }
+
+ public function getCartItems($userId) {
+ try {
+ $query = "SELECT * FROM cart WHERE user_id = ?";
+ $stmt = $this->conn->prepare($query);
+ $stmt->execute([$userId]);
+ return $stmt->fetchAll(\PDO::FETCH_ASSOC);
+ } catch (\Exception $e) {
+ error_log("Error al obtener items del carrito: " . $e->getMessage());
+ return [];
+ }
+ }
+
+ public function updateCartQuantity($cartId, $quantity) {
+ try {
+ $query = "UPDATE cart SET quantity = ? WHERE id = ?";
+ $stmt = $this->conn->prepare($query);
+ $stmt->execute([$quantity, $cartId]);
+ return ['success' => true, 'message' => '¡Cantidad actualizada!'];
+ } catch (\Exception $e) {
+ error_log("Error al actualizar cantidad: " . $e->getMessage());
+ return ['success' => false, 'message' => 'Error al actualizar cantidad'];
+ }
+ }
+
+ public function deleteCartItem($cartId) {
+ try {
+ $query = "DELETE FROM cart WHERE id = ?";
+ $stmt = $this->conn->prepare($query);
+ $stmt->execute([$cartId]);
+ return true;
+ } catch (\Exception $e) {
+ error_log("Error al eliminar item: " . $e->getMessage());
+ return false;
+ }
+ }
+
+ public function deleteAllCartItems($userId) {
+ try {
+ $query = "DELETE FROM cart WHERE user_id = ?";
+ $stmt = $this->conn->prepare($query);
+ $stmt->execute([$userId]);
+ return true;
+ } catch (\Exception $e) {
+ error_log("Error al eliminar todos los items: " . $e->getMessage());
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Controllers/SearchController.php b/src/Controllers/SearchController.php
new file mode 100644
index 00000000..80a74403
--- /dev/null
+++ b/src/Controllers/SearchController.php
@@ -0,0 +1,23 @@
+conn = $conn;
+ }
+
+ public function searchProducts($searchTerm) {
+ try {
+ $searchTerm = "%{$searchTerm}%";
+ $stmt = $this->conn->prepare("SELECT * FROM products WHERE name LIKE ?");
+ $stmt->execute([$searchTerm]);
+
+ return $stmt->fetchAll(\PDO::FETCH_ASSOC);
+ } catch (\Exception $e) {
+ error_log("Error en búsqueda: " . $e->getMessage());
+ return [];
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Controllers/UserController.php b/src/Controllers/UserController.php
new file mode 100644
index 00000000..d1cedf75
--- /dev/null
+++ b/src/Controllers/UserController.php
@@ -0,0 +1,114 @@
+conn = $conn;
+ }
+
+ private function hashPassword($password) {
+ return password_hash($password, PASSWORD_BCRYPT, ['cost' => self::HASH_COST]);
+ }
+
+ private function verifyPassword($password, $hashedPassword) {
+ return password_verify($password, $hashedPassword);
+ }
+
+ public function registerUser($userData) {
+ try {
+ $user = new User();
+ $user->setName($userData['name']);
+ $user->setEmail($userData['email']);
+ $user->setUserType($userData['user_type'] ?? 'user');
+
+ // Verificar si el correo ya existe
+ $stmt = $this->conn->prepare("SELECT id FROM users WHERE email = ?");
+ $stmt->execute([$user->getEmail()]);
+ if($stmt->fetch()) {
+ return ['success' => false, 'message' => 'El correo ya está registrado'];
+ }
+
+ // Usar password_hash en lugar de md5
+ $stmt = $this->conn->prepare(
+ "INSERT INTO users (name, email, password, user_type)
+ VALUES (?, ?, ?, ?)"
+ );
+
+ $stmt->execute([
+ $user->getName(),
+ $user->getEmail(),
+ $this->hashPassword($userData['password']),
+ $user->getUserType()
+ ]);
+
+ return ['success' => true, 'message' => 'Registro exitoso!'];
+ } catch (\Exception $e) {
+ error_log("Error en registro: " . $e->getMessage());
+ return ['success' => false, 'message' => 'Error en el registro'];
+ }
+ }
+
+ public function loginUser($email, $password) {
+ try {
+ $stmt = $this->conn->prepare("SELECT * FROM users WHERE email = ?");
+ $stmt->execute([$email]);
+ $user = $stmt->fetch(\PDO::FETCH_ASSOC);
+
+ if ($user && $this->verifyPassword($password, $user['password'])) {
+ $_SESSION['user_id'] = $user['id'];
+ $_SESSION['user_name'] = $user['name'];
+ $_SESSION['user_type'] = $user['user_type'];
+ $_SESSION['user_email'] = $user['email'];
+ return ['success' => true, 'user_type' => $user['user_type']];
+ }
+
+ return ['success' => false, 'message' => 'Correo o contraseña incorrectos'];
+ } catch (\Exception $e) {
+ error_log("Error en login: " . $e->getMessage());
+ return ['success' => false, 'message' => 'Error en el inicio de sesión'];
+ }
+ }
+
+ public function logout() {
+ try {
+ // Asegurarse de que la sesión está iniciada
+ if (session_status() === PHP_SESSION_NONE) {
+ session_start();
+ }
+
+ // Limpiar todas las variables de sesión
+ $_SESSION = array();
+
+ // Destruir la sesión
+ session_destroy();
+
+ // Redireccionar al login
+ header('location: ../auth/login.php');
+ exit();
+ } catch (\Exception $e) {
+ error_log("Error en logout: " . $e->getMessage());
+ return ['success' => false, 'message' => 'Error al cerrar sesión'];
+ }
+ }
+
+ public function getUserById($userId) {
+ try {
+ $stmt = $this->conn->prepare("SELECT id, name, email, user_type FROM users WHERE id = ?");
+ $stmt->execute([$userId]);
+ return $stmt->fetch(\PDO::FETCH_ASSOC);
+ } catch (\Exception $e) {
+ error_log("Error al obtener usuario: " . $e->getMessage());
+ return null;
+ }
+ }
+
+ // Alias para mantener compatibilidad
+ public function register($userData) {
+ return $this->registerUser($userData);
+ }
+}
\ No newline at end of file
diff --git a/src/Exceptions/DatabaseException.php b/src/Exceptions/DatabaseException.php
new file mode 100644
index 00000000..c6196eff
--- /dev/null
+++ b/src/Exceptions/DatabaseException.php
@@ -0,0 +1,8 @@
+id; }
+ public function getUserId() { return $this->userId; }
+ public function getName() { return $this->name; }
+ public function getPrice() { return $this->price; }
+ public function getQuantity() { return $this->quantity; }
+ public function getImage() { return $this->image; }
+
+ // Setters
+ public function setUserId($userId) { $this->userId = $userId; }
+ public function setName($name) { $this->name = $name; }
+ public function setPrice($price) { $this->price = $price; }
+ public function setQuantity($quantity) { $this->quantity = $quantity; }
+ public function setImage($image) { $this->image = $image; }
+}
\ No newline at end of file
diff --git a/src/Models/Message.php b/src/Models/Message.php
new file mode 100644
index 00000000..b4f17dcd
--- /dev/null
+++ b/src/Models/Message.php
@@ -0,0 +1,47 @@
+id; }
+ public function getUserId() { return $this->userId; }
+ public function getName() { return $this->name; }
+ public function getEmail() { return $this->email; }
+ public function getNumber() { return $this->number; }
+ public function getMessage() { return $this->message; }
+
+ // Setters
+ public function setUserId($userId) { $this->userId = $userId; }
+ public function setName($name) { $this->name = $name; }
+ public function setEmail($email) { $this->email = $email; }
+ public function setNumber($number) { $this->number = $number; }
+ public function setMessage($message) { $this->message = $message; }
+
+ public function exists($conn) {
+ $sql = "SELECT id FROM message WHERE user_id = ? AND message = ?";
+ $stmt = $conn->prepare($sql);
+ $stmt->execute([$this->userId, $this->message]);
+ return $stmt->rowCount() > 0;
+ }
+
+ public function save($conn) {
+ $sql = "INSERT INTO message (user_id, name, email, number, message) VALUES (?, ?, ?, ?, ?)";
+ $stmt = $conn->prepare($sql);
+ return $stmt->execute([
+ $this->userId,
+ $this->name,
+ $this->email,
+ $this->number,
+ $this->message
+ ]);
+ }
+
+ public function setId($id) { $this->id = $id; }
+}
\ No newline at end of file
diff --git a/src/Models/Order.php b/src/Models/Order.php
new file mode 100644
index 00000000..644852e1
--- /dev/null
+++ b/src/Models/Order.php
@@ -0,0 +1,46 @@
+id; }
+ public function getUserId() { return $this->userId; }
+ public function getName() { return $this->name; }
+ public function getNumber() { return $this->number; }
+ public function getEmail() { return $this->email; }
+ public function getMethod() { return $this->method; }
+ public function getAddress() { return $this->address; }
+ public function getTotalProducts() { return $this->totalProducts; }
+ public function getTotalPrice() { return $this->totalPrice; }
+ public function getPlacedOn() { return $this->placedOn; }
+ public function getPaymentStatus() { return $this->paymentStatus; }
+
+ // Setters
+ public function setUserId($userId) { $this->userId = $userId; }
+ public function setName($name) { $this->name = $name; }
+ public function setNumber($number) { $this->number = $number; }
+ public function setEmail($email) { $this->email = $email; }
+ public function setMethod($method) { $this->method = $method; }
+ public function setAddress($address) { $this->address = $address; }
+ public function setTotalProducts($totalProducts) { $this->totalProducts = $totalProducts; }
+ public function setTotalPrice($totalPrice) { $this->totalPrice = $totalPrice; }
+ public function setPaymentStatus($status) { $this->paymentStatus = $status; }
+ public function setPlacedOn($placedOn) { $this->placedOn = $placedOn; }
+
+ public function setId($id) {
+ $this->id = $id;
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/src/Models/Product.php b/src/Models/Product.php
new file mode 100644
index 00000000..d85711e8
--- /dev/null
+++ b/src/Models/Product.php
@@ -0,0 +1,24 @@
+id; }
+ public function getName() { return $this->name; }
+ public function getPrice() { return $this->price; }
+ public function getImage() { return $this->image; }
+
+ // Setters
+ public function setName($name) { $this->name = $name; }
+ public function setPrice($price) { $this->price = $price; }
+ public function setImage($image) { $this->image = $image; }
+
+ public function setId($id) {
+ $this->id = $id;
+ }
+}
\ No newline at end of file
diff --git a/src/Models/User.php b/src/Models/User.php
new file mode 100644
index 00000000..c896385f
--- /dev/null
+++ b/src/Models/User.php
@@ -0,0 +1,38 @@
+userType = 'user';
+ }
+
+ // Getters
+ public function getId() { return $this->id; }
+ public function getName() { return $this->name; }
+ public function getEmail() { return $this->email; }
+ public function getUserType() { return $this->userType; }
+
+ // Setters
+ public function setName($name) { $this->name = $name; }
+ public function setEmail($email) { $this->email = $email; }
+ public function setPassword($password) {
+ $this->password = password_hash($password, PASSWORD_DEFAULT);
+ }
+ public function setUserType($userType) { $this->userType = $userType; }
+ public function setId($id) {
+ $this->id = $id;
+ return $this;
+ }
+
+ public function exists($conn) {
+ $stmt = $conn->prepare("SELECT id FROM users WHERE id = ?");
+ $stmt->execute([$this->id]);
+ return $stmt->rowCount() > 0;
+ }
+}
\ No newline at end of file
diff --git a/src/Views/admin/admin_contacts.php b/src/Views/admin/admin_contacts.php
new file mode 100644
index 00000000..7acee1aa
--- /dev/null
+++ b/src/Views/admin/admin_contacts.php
@@ -0,0 +1,75 @@
+connect();
+$adminController = new AdminController($conn);
+
+if(isset($_GET['delete'])){
+ $delete_id = $_GET['delete'];
+ if($adminController->deleteMessage($delete_id)) {
+ header('location:admin_contacts.php');
+ exit();
+ }
+}
+
+?>
+
+
+
+
+
+
+
+ Mensajes
+
+
+
+
+
+
+
+
+ mensajes
+
+
+ getAllMessages();
+ if(!empty($messages)){
+ foreach($messages as $message){
+ ?>
+
+
user id : getUserId(); ?>
+
nombre : getName(); ?>
+
numero : getNumber(); ?>
+
email : getEmail(); ?>
+
mensaje : getMessage(); ?>
+
eliminar mensaje
+
+ ¡No tienes mensajes!';
+ }
+ ?>
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Views/admin/admin_orders.php b/src/Views/admin/admin_orders.php
new file mode 100644
index 00000000..2c706c40
--- /dev/null
+++ b/src/Views/admin/admin_orders.php
@@ -0,0 +1,96 @@
+connect();
+$adminController = new AdminController($conn);
+
+if(isset($_POST['update_order'])){
+ $order_update_id = $_POST['order_id'];
+ $update_payment = $_POST['update_payment'];
+
+ if($adminController->updateOrderStatus($order_update_id, $update_payment)) {
+ $message[] = 'El estado del pago ha sido actualizado!';
+ }
+}
+
+if(isset($_GET['delete'])){
+ $delete_id = $_GET['delete'];
+ if($adminController->deleteOrder($delete_id)) {
+ header('location:admin_orders.php');
+ exit();
+ }
+}
+?>
+
+
+
+
+
+
+
+ Pedidos
+
+
+
+
+
+
+
+
+ pedidos realizados
+
+
+ getAllOrders();
+ if(!empty($orders)){
+ foreach($orders as $order){
+ ?>
+
+
user id : getUserId(); ?>
+
estimado : getPlacedOn(); ?>
+
nombre : getName(); ?>
+
numero : getNumber(); ?>
+
email : getEmail(); ?>
+
direccion : getAddress(); ?>
+
productos totales : getTotalProducts(); ?>
+
Precio total : S/. getTotalPrice(); ?> soles
+
Método de pago : getMethod(); ?>
+
+
+ ¡Aún no hay pedidos realizados!';
+ }
+ ?>
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Views/admin/admin_page.php b/src/Views/admin/admin_page.php
new file mode 100644
index 00000000..98eccb89
--- /dev/null
+++ b/src/Views/admin/admin_page.php
@@ -0,0 +1,98 @@
+connect();
+$adminController = new AdminController($conn);
+
+// Obtener datos del dashboard
+$dashboardData = $adminController->getDashboardData();
+
+?>
+
+
+
+
+
+
+
+ Panel de Administración
+
+
+
+
+
+
+
+
+ Panel de Control
+
+
+
+
+
S/. Soles
+
Total Pendientes
+
+
+
+
+
S/. Soles
+
Pagos Completados
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Views/admin/admin_products.php b/src/Views/admin/admin_products.php
new file mode 100644
index 00000000..3ac13d3c
--- /dev/null
+++ b/src/Views/admin/admin_products.php
@@ -0,0 +1,150 @@
+connect();
+$adminController = new AdminController($conn);
+
+if(isset($_POST['add_product'])) {
+ $result = $adminController->addProduct($_POST, $_FILES);
+ $message[] = $result['message'];
+}
+
+if(isset($_GET['delete'])) {
+ $result = $adminController->deleteProduct($_GET['delete']);
+ if($result['success']) {
+ header('location:admin_products.php');
+ exit();
+ } else {
+ $message[] = $result['message'];
+ }
+}
+
+if(isset($_POST['update_product'])) {
+ $result = $adminController->updateProduct($_POST, $_FILES);
+ if($result['success']) {
+ header('location:admin_products.php');
+ exit();
+ }
+ $message[] = $result['message'];
+}
+
+$products = $adminController->getAllProducts();
+
+?>
+
+
+
+
+
+
+
+ products
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ productos de la tienda
+
+
+
+
+
+
+
+
+
+
+
+ 0){
+ foreach($products as $product){
+ ?>
+
+ ¡Aún no hay productos añadidos!';
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Views/admin/admin_users.php b/src/Views/admin/admin_users.php
new file mode 100644
index 00000000..8824a47d
--- /dev/null
+++ b/src/Views/admin/admin_users.php
@@ -0,0 +1,70 @@
+connect();
+$adminController = new AdminController($conn);
+
+if(isset($_GET['delete'])){
+ $delete_id = $_GET['delete'];
+ if($adminController->deleteUser($delete_id)) {
+ header('location: admin_users.php');
+ exit();
+ }
+}
+?>
+
+
+
+
+
+
+
+ Usuarios
+
+
+
+
+
+
+
+
+ Cuentas de usuario
+
+
+ getAllUsers();
+ foreach($users as $user){
+ ?>
+
+
user id : getId(); ?>
+
nombre de usuario : getName(); ?>
+
email : getEmail(); ?>
+
tipo de usuario :
+ getUserType(); ?>
+
eliminar usuario
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Views/auth/login.php b/src/Views/auth/login.php
new file mode 100644
index 00000000..b4413a68
--- /dev/null
+++ b/src/Views/auth/login.php
@@ -0,0 +1,70 @@
+connect();
+$userController = new UserController($conn);
+
+if(isset($_POST['submit'])){
+ $email = $_POST['email'];
+ $password = $_POST['password'];
+
+ $result = $userController->loginUser($email, $password);
+
+ if($result['success']){
+ if($result['user_type'] == 'admin'){
+ header('location:../admin/admin_page.php');
+ }else{
+ header('location:../usuario/home.php');
+ }
+ exit();
+ } else {
+ $message[] = $result['message'];
+ }
+}
+?>
+
+
+
+
+
+
+
+ login
+
+
+
+
+
+
+ '.$msg.'
+
+
+ ';
+ }
+}
+?>
+
+
+
+
+
diff --git a/src/Views/auth/logout.php b/src/Views/auth/logout.php
new file mode 100644
index 00000000..c9cfc8b1
--- /dev/null
+++ b/src/Views/auth/logout.php
@@ -0,0 +1,13 @@
+connect();
+$userController = new UserController($conn);
+$userController->logout();
\ No newline at end of file
diff --git a/src/Views/auth/register.php b/src/Views/auth/register.php
new file mode 100644
index 00000000..1e72e1db
--- /dev/null
+++ b/src/Views/auth/register.php
@@ -0,0 +1,82 @@
+connect();
+$userController = new UserController($conn);
+
+if (isset($_POST['submit'])) {
+ $name = $_POST['name'];
+ $email = $_POST['email'];
+ $password = $_POST['password'];
+ $cpassword = $_POST['cpassword'];
+
+ if ($password == $cpassword) {
+ $userData = [
+ 'name' => $name,
+ 'email' => $email,
+ 'password' => $password
+ ];
+
+ $result = $userController->register($userData);
+
+ if ($result['success']) {
+ header('Location: login.php');
+ exit();
+ } else {
+ $message[] = $result['message'];
+ }
+ } else {
+ $message[] = 'Las contraseñas no coinciden!';
+ }
+}
+?>
+
+
+
+
+
+
+
+ Registro
+
+
+
+
+
+
+
+
+
+ = $msg ?>
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Views/components/admin_header.php b/src/Views/components/admin_header.php
new file mode 100644
index 00000000..af25d46b
--- /dev/null
+++ b/src/Views/components/admin_header.php
@@ -0,0 +1,50 @@
+
+ '.htmlspecialchars($msg).'
+
+
+ ';
+ }
+}
+?>
+
+
\ No newline at end of file
diff --git a/src/Views/components/footer.php b/src/Views/components/footer.php
new file mode 100644
index 00000000..4e1c23ec
--- /dev/null
+++ b/src/Views/components/footer.php
@@ -0,0 +1,26 @@
+
\ No newline at end of file
diff --git a/src/Views/components/header.php b/src/Views/components/header.php
new file mode 100644
index 00000000..dbf712bb
--- /dev/null
+++ b/src/Views/components/header.php
@@ -0,0 +1,127 @@
+
+ ' . htmlspecialchars($msg) . '
+
+
+ ';
+ }
+}
+
+try {
+ $stmt = $conn->prepare("SELECT COUNT(*) FROM cart WHERE user_id = ?");
+ $stmt->execute([$user_id]);
+ $cart_rows_number = $stmt->fetchColumn();
+} catch (\PDOException $e) {
+ error_log("Error al obtener items del carrito: " . $e->getMessage());
+ $cart_rows_number = 0;
+}
+?>
+
+
+
+
+
+
diff --git a/src/Views/usuario/about.php b/src/Views/usuario/about.php
new file mode 100644
index 00000000..d9410665
--- /dev/null
+++ b/src/Views/usuario/about.php
@@ -0,0 +1,225 @@
+connect();
+$productController = new ProductController($conn);
+
+$user_id = $_SESSION['user_id'];
+?>
+
+
+
+
+
+
+
+
+ Nosotros
+
+
+
+
+
+
+
+
+
+
+
Nosotros
+
Inicio / Nosotros
+
+
+
+
+
+
+
+
+
¿Por qué elegirnos?
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eveniet voluptatibus aut hic molestias, reiciendis natus fuga, cumque excepturi veniam ratione iure.
+
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Impedit quos enim minima ipsa dicta officia corporis ratione saepe sed adipisci?
+
Contáctanos
+
+
+
+
+
+ Reseñas de clientes
+
+
+
+
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sunt ad, quo labore fugiat nam accusamus quia. Ducimus repudiandae dolore placeat.
+
+
+
+
+
+
+
+
Jose Alvarado
+
+
+
+
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sunt ad, quo labore fugiat nam accusamus quia. Ducimus repudiandae dolore placeat.
+
+
+
+
+
+
+
+
Gustavo Tacs
+
+
+
+
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sunt ad, quo labore fugiat nam accusamus quia. Ducimus repudiandae dolore placeat.
+
+
+
+
+
+
+
+
Gustavo Res
+
+
+
+
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sunt ad, quo labore fugiat nam accusamus quia. Ducimus repudiandae dolore placeat.
+
+
+
+
+
+
+
+
Karen Marca
+
+
+
+
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sunt ad, quo labore fugiat nam accusamus quia. Ducimus repudiandae dolore placeat.
+
+
+
+
+
+
+
+
Olvier Ato
+
+
+
+
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sunt ad, quo labore fugiat nam accusamus quia. Ducimus repudiandae dolore placeat.
+
+
+
+
+
+
+
+
Tomas Torres
+
+
+
+
+
+
+ Grandes autores
+
+
+
+
+
Autor 1
+
+
+
+
+
+
Autor 2
+
+
+
+
+
+
Autor 3
+
+
+
+
+
+
+
Autor 4
+
+
+
+
+
+
+
Autor 5
+
+
+
+
+
+
Autor 6
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Views/usuario/cart.php b/src/Views/usuario/cart.php
new file mode 100644
index 00000000..b62de966
--- /dev/null
+++ b/src/Views/usuario/cart.php
@@ -0,0 +1,130 @@
+connect();
+$productController = new ProductController($conn);
+
+$user_id = $_SESSION['user_id'];
+
+if(isset($_POST['update_cart'])) {
+ $result = $productController->updateCartQuantity(
+ $_POST['cart_id'],
+ $_POST['cart_quantity']
+ );
+ $message[] = $result['message'];
+}
+
+if(isset($_GET['delete'])) {
+ if($productController->deleteCartItem($_GET['delete'])) {
+ header('location: cart.php');
+ exit();
+ }
+}
+
+if(isset($_GET['delete_all'])) {
+ if($productController->deleteAllCartItems($user_id)) {
+ header('location: cart.php');
+ exit();
+ }
+}
+
+$cartItems = $productController->getCartItems($user_id);
+?>
+
+
+
+
+
+
+
+ Carrito
+
+
+
+
+
+
+
+
+
+
Carrito de Compras
+
Inicio / Carrito
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Views/usuario/checkout.php b/src/Views/usuario/checkout.php
new file mode 100644
index 00000000..187526e0
--- /dev/null
+++ b/src/Views/usuario/checkout.php
@@ -0,0 +1,135 @@
+connect();
+$orderController = new OrderController($conn);
+$productController = new ProductController($conn);
+
+$user_id = $_SESSION['user_id'];
+$message = [];
+
+if(isset($_POST['order_btn'])) {
+ $result = $orderController->createOrder($_POST, $user_id);
+ $message[] = $result['message'];
+}
+
+$cartItems = $productController->getCartItems($user_id);
+$grand_total = 0;
+?>
+
+
+
+
+
+
+
+ Checkout
+
+
+
+
+
+
+
+
+
+
Verificar Pedido
+
Inicio / Verificar
+
+
+
+
+
+
+ (S/. )
+
+ Tu carrito está vacío
';
+ }
+ ?>
+ Total a pagar: S/. Soles
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Views/usuario/contact.php b/src/Views/usuario/contact.php
new file mode 100644
index 00000000..294e3bd8
--- /dev/null
+++ b/src/Views/usuario/contact.php
@@ -0,0 +1,66 @@
+connect();
+$contactController = new ContactController($conn);
+
+$user_id = $_SESSION['user_id'];
+
+if(isset($_POST['send'])) {
+ $_POST['user_id'] = $user_id;
+ $result = $contactController->sendMessage($_POST);
+ $message[] = $result['message'];
+}
+?>
+
+
+
+
+
+
+
+ Contacto
+
+
+
+
+
+
+
+
+
+
Contáctanos
+
Inicio / Contáctanos
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Views/usuario/home.php b/src/Views/usuario/home.php
new file mode 100644
index 00000000..52c9aa4f
--- /dev/null
+++ b/src/Views/usuario/home.php
@@ -0,0 +1,105 @@
+connect();
+$productController = new ProductController($conn);
+
+if(isset($_POST['add_to_cart'])) {
+ $result = $productController->addToCart($_SESSION['user_id'], $_POST);
+ $message[] = $result['message'];
+}
+
+$products = $productController->getLatestProducts();
+?>
+
+
+
+
+
+
+
+ Home
+
+
+
+
+
+
+
+
+
+
+
Peliculas y series en tus manos
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Excepturi, quod? Reiciendis ut porro iste totam.
+
Descubrir Más
+
+
+
+
+ ÚLTIMOS productos
+
+
+
+
+
+ getName()); ?>
+ S/. getPrice()); ?> Soles
+
+
+
+
+
+
+
+
+
¡Aún no hay productos añadidos!
+
+
+
+
+
+
+
+
+
+
+
+
+
Sobre nosotros
+
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Impedit quos enim minima ipsa dicta officia corporis ratione saepe sed adipisci?
+
Leer más
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Views/usuario/orders.php b/src/Views/usuario/orders.php
new file mode 100644
index 00000000..850db722
--- /dev/null
+++ b/src/Views/usuario/orders.php
@@ -0,0 +1,82 @@
+connect();
+$orderController = new OrderController($conn);
+
+$user_id = $_SESSION['user_id'];
+
+$orders = $orderController->getUserOrders($user_id);
+?>
+
+
+
+
+
+
+
+ Pedidos
+
+
+
+
+
+
+
+
+
+
Pedidos
+
Inicio / Pedidos
+
+
+
+ Pedidos realizados
+
+
+
+
Fecha : getPlacedOn(); ?>
+
Nombre : getName(); ?>
+
Número : getNumber(); ?>
+
Email : getEmail(); ?>
+
Dirección : getAddress(); ?>
+
Método de pago : getMethod(); ?>
+
Tus pedidos : getTotalProducts(); ?>
+
Precio total : S/. getTotalPrice(); ?> Soles
+
Estado del pago :
+
+ getPaymentStatus(); ?>
+
+
+
+ ¡Aún no se han realizado pedidos!';
+ }
+ ?>
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Views/usuario/search_page.php b/src/Views/usuario/search_page.php
new file mode 100644
index 00000000..65e3c587
--- /dev/null
+++ b/src/Views/usuario/search_page.php
@@ -0,0 +1,110 @@
+connect();
+$searchController = new SearchController($conn);
+$productController = new ProductController($conn);
+
+$user_id = $_SESSION['user_id'];
+$message = [];
+$products = [];
+
+// Procesar búsqueda
+if(isset($_POST['submit'])) {
+ $search_term = $_POST['search'];
+ $products = $searchController->searchProducts($search_term);
+}
+
+// Procesar agregar al carrito
+if(isset($_POST['add_to_cart'])) {
+ $result = $productController->addToCart(
+ $user_id,
+ [
+ 'product_name' => $_POST['product_name'],
+ 'product_price' => $_POST['product_price'],
+ 'product_quantity' => $_POST['product_quantity'],
+ 'product_image' => $_POST['product_image']
+ ]
+ );
+ $message[] = $result['message'];
+}
+?>
+
+
+
+
+
+
+
+ Búsqueda
+
+
+
+
+
+
+
+
+
+
Busca tus series o películas
+
Inicio / Buscar
+
+
+
+
+
+
+
+
+
+
+ S/. Soles
+
+
+
+
+
+
+ ¡No se han encontrado resultados!';
+ }
+ } else {
+ echo '
¡Busca algo!
';
+ }
+ ?>
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Views/usuario/shop.php b/src/Views/usuario/shop.php
new file mode 100644
index 00000000..b4d2e7b9
--- /dev/null
+++ b/src/Views/usuario/shop.php
@@ -0,0 +1,79 @@
+connect();
+$productController = new ProductController($conn);
+
+if(isset($_POST['add_to_cart'])) {
+ $result = $productController->addToCart($_SESSION['user_id'], $_POST);
+ $message[] = $result['message'];
+}
+
+$products = $productController->getAllProducts();
+?>
+
+
+
+
+
+
+
+ Tienda
+
+
+
+
+
+
+
+
+
+
Nuestra tienda
+
Inicio / Tienda
+
+
+
+ Últimos productos
+
+
+
+
+
+ getName()); ?>
+ S/. getPrice()); ?> Soles
+
+
+
+
+
+
+
+
+
¡Aún no hay productos añadidos!
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/assets/styles/responsive.css b/src/assets/styles/responsive.css
new file mode 100644
index 00000000..80157134
--- /dev/null
+++ b/src/assets/styles/responsive.css
@@ -0,0 +1,472 @@
+@charset "utf-8";
+
+@media only screen and (max-width: 1600px) {
+}
+
+@media only screen and (max-width: 1540px) {
+}
+
+@media only screen and (max-width: 1380px) {
+}
+
+@media only screen and (max-width: 1280px) {
+}
+
+@media only screen and (max-width: 1199px) {
+ .main_slider {
+ min-height: 475px;
+ height: calc(100vw / 1.714);
+ }
+ .main_slider_content {
+ width: 80%;
+ }
+ .banner_item {
+ height: 220px;
+ }
+ .product-item {
+ height: 360px;
+ }
+ .product {
+ height: 320px;
+ }
+ .timer li {
+ width: 90px;
+ height: 90px;
+ }
+ .timer_num {
+ font-size: 36px;
+ font-weight: 500;
+ }
+ .timer_unit {
+ margin-top: 5px;
+ }
+ .blog_title {
+ font-size: 20px;
+ }
+}
+
+@media only screen and (max-width: 1024px) {
+}
+
+@media only screen and (max-width: 991px) {
+ h1 {
+ font-size: 48px;
+ }
+ h2 {
+ font-size: 36px;
+ }
+ .main_slider_content h6 {
+ margin-bottom: 23px;
+ }
+ .shop_now_button {
+ margin-top: 26px;
+ }
+ .grid-item {
+ width: 25%;
+ }
+ .top_nav {
+ display: none;
+ }
+ .navbar_menu {
+ display: none;
+ }
+ .hamburger_container {
+ display: block;
+ }
+ .main_slider {
+ margin-top: 100px;
+ }
+ .main_slider_content {
+ width: 100%;
+ padding-right: 15px;
+ }
+ .banner_item {
+ height: 160px;
+ }
+ .banner_category {
+ min-width: 160px;
+ height: 40px;
+ }
+ .banner_category a {
+ font-size: 16px;
+ line-height: 40px;
+ }
+ .product-item {
+ width: 25%;
+ }
+ .deal_ofthe_week_col {
+ display: -webkit-box;
+ display: -moz-box;
+ display: -ms-flexbox;
+ display: -webkit-flex;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ }
+ .deal_ofthe_week_content {
+ position: relative;
+ top: auto;
+ left: auto;
+ height: auto;
+ }
+ .deal_ofthe_week_img {
+ text-align: center;
+ }
+ .timer li {
+ width: 70px;
+ height: 70px;
+ }
+ .timer_num {
+ font-size: 28px;
+ }
+ .timer_unit {
+ margin-top: 1px;
+ font-size: 14px;
+ }
+ .benefit_col {
+ margin-bottom: 30px;
+ }
+ .benefit_col:last-child {
+ margin-bottom: 0px;
+ }
+ .blog_item_col {
+ margin-bottom: 30px;
+ }
+ .blog_item_col:last-child {
+ margin-bottom: 0px;
+ }
+ .blog_item {
+ height: 440px;
+ }
+ .blog_title {
+ font-size: 24px;
+ }
+ .newsletter_text {
+ height: auto;
+ margin-top: 30px;
+ }
+ .newsletter_text p {
+ margin-top: 5px;
+ }
+ .newsletter_form {
+ height: auto;
+ margin-top: 30px;
+ margin-bottom: 40px;
+ }
+
+ .footer_social {
+ height: auto;
+ margin-top: 20px;
+ margin-bottom: 65px;
+ }
+}
+
+@media only screen and (max-width: 959px) {
+}
+
+@media only screen and (max-width: 880px) {
+}
+
+@media only screen and (max-width: 768px) {
+}
+
+@media only screen and (max-width: 767px) {
+ h1 {
+ font-size: 36px;
+ }
+ h2 {
+ font-size: 24px;
+ }
+ h6 {
+ font-size: 12px;
+ }
+ .main_slider_content h6 {
+ margin-bottom: 20px;
+ }
+ .shop_now_button {
+ margin-top: 23px;
+ }
+ .red_button a {
+ font-size: 12px;
+ }
+ .banner_item {
+ margin-bottom: 30px;
+ height: 210px;
+ }
+ .new_arrivals_title {
+ margin-top: 44px;
+ }
+ .product-item {
+ width: 33.333333333333%;
+ height: 345px;
+ }
+ .product {
+ height: 305px;
+ }
+ .grid_sorting_button {
+ font-size: 13px;
+ padding-left: 20px;
+ padding-right: 20px;
+ min-width: 80px;
+ }
+ .product_slider_container {
+ height: auto;
+ }
+ .product_slider_item .product-item {
+ height: 380px;
+ }
+ .benefit_col {
+ margin-bottom: 15px;
+ }
+ .blog_item {
+ height: 372px;
+ }
+ .newsletter_form {
+ margin-bottom: 40px;
+ }
+ .newsletter_submit_btn {
+ margin-top: 15px;
+ }
+}
+
+@media only screen and (max-width: 575px) {
+ .hamburger_menu {
+ right: -100%;
+ width: 100%;
+ }
+ .product-item {
+ width: 50%;
+ height: 420px;
+ }
+ .product {
+ height: 380px;
+ }
+ .blog_item {
+ height: calc((100vw - 30px) / 1.37);
+ }
+ .cr {
+ display: block;
+ margin-right: 0px;
+ margin-bottom: 30px;
+ font-size: 13px;
+ line-height: 20px;
+ }
+ .footer_nav {
+ margin-bottom: 20px;
+ }
+ .footer_nav li {
+ display: block;
+ margin-right: 0px;
+ }
+ .footer_nav li a {
+ font-size: 13px;
+ }
+}
+
+@media only screen and (max-width: 539px) {
+}
+
+@media only screen and (max-width: 480px) {
+}
+
+@media only screen and (max-width: 479px) {
+ .logo_container a {
+ font-size: 12px;
+ }
+ .hamburger_container {
+ margin-left: 25px;
+ }
+ .hamburger_container i {
+ font-size: 16px;
+ }
+ .hamburger_close {
+ top: 14px;
+ right: 4px;
+ }
+ .hamburger_close i {
+ font-size: 20px;
+ }
+ .hamburger_menu_content {
+ padding-right: 15px;
+ padding-top: 70px;
+ }
+ .menu_item {
+ border-bottom-color: rgba(181, 174, 196, 0.5);
+ }
+ .menu_item > a {
+ font-size: 12px;
+ line-height: 35px;
+ height: 35px;
+ }
+ .menu_selection li a {
+ font-size: 12px;
+ line-height: 35px;
+ height: 35px;
+ }
+ .navbar {
+ height: 70px;
+ }
+ .navbar_user li a {
+ width: 30px;
+ height: 30px;
+ font-size: 12px;
+ }
+ .checkout_items {
+ width: 15px;
+ height: 15px;
+ font-size: 10px;
+ }
+ .main_slider {
+ height: calc(100vh - 70px);
+ min-height: auto;
+ margin-top: 70px;
+ }
+ .main_slider_content h6 {
+ margin-bottom: 15px;
+ }
+ .main_slider_content h1 {
+ font-size: 24px;
+ }
+ .shop_now_button {
+ margin-top: 15px;
+ width: 100px;
+ height: 35px;
+ }
+ .red_button a {
+ font-size: 10px;
+ }
+ .banner_item {
+ height: calc((100vw - 30px) / 2.6);
+ }
+ .grid_sorting_button {
+ font-size: 12px;
+ padding-left: 10px;
+ padding-right: 10px;
+ min-width: 60px;
+ height: 35px;
+ }
+ .product-item {
+ width: 100%;
+ height: auto;
+ }
+ .product {
+ height: auto;
+ }
+ .product_name a {
+ font-size: 12px;
+ }
+ .add_to_cart_button {
+ margin-top: 20px;
+ }
+ .deal_ofthe_week_img {
+ height: 400px;
+ }
+ .timer {
+ margin-top: 42px;
+ }
+ .timer li {
+ width: 50px;
+ height: 50px;
+ }
+ .section_title::after {
+ top: calc(100% + 8px);
+ height: 3px;
+ }
+ .timer_num {
+ margin-top: 4px;
+ font-size: 16px;
+ }
+ .timer_unit {
+ margin-top: -9px;
+ font-size: 10px;
+ }
+ .deal_ofthe_week_button {
+ margin-top: 36px;
+ }
+
+ .product_slider_item .product-item {
+ width: 100%;
+ height: auto;
+ }
+ .product_slider_item .product-item:hover::after {
+ box-shadow: none;
+ }
+ .product_slider_item .product-item .product {
+ height: auto;
+ }
+ .product_slider_item .product-item .product_info {
+ padding-bottom: 30px;
+ }
+ .blog_title {
+ font-size: 16px;
+ }
+ .blog_meta {
+ font-size: 10px;
+ }
+ .blog_more {
+ font-size: 12px;
+ margin-top: 5px;
+ }
+ .newsletter_form {
+ margin-top: 22px;
+ }
+ .newsletter_text h4 {
+ font-size: 20px;
+ }
+ .newsletter_text p {
+ font-size: 13px;
+ margin-top: 5px;
+ }
+ .newsletter_submit_btn {
+ font-size: 12px;
+ }
+ #newsletter_email {
+ height: 40px;
+ width: 100%;
+ padding-left: 15px;
+ }
+ .newsletter_submit_btn {
+ height: 36px;
+ width: 130px;
+ }
+ #newsletter_email::-webkit-input-placeholder {
+ font-size: 12px !important;
+ padding-left: 0px;
+ }
+ #newsletter_email:-moz-placeholder {
+ font-size: 12px !important;
+ padding-left: 0px;
+ }
+ #newsletter_email::-moz-placeholder {
+ font-size: 12px !important;
+ padding-left: 0px;
+ }
+ #newsletter_email:-ms-input-placeholder {
+ font-size: 12px !important;
+ padding-left: 0px;
+ }
+ #newsletter_email::placeholder {
+ font-size: 12px !important;
+ padding-left: 0px;
+ }
+
+ .footer_nav_container {
+ margin-top: 35px;
+ }
+ .cr {
+ margin-bottom: 20px;
+ }
+ .footer_social {
+ margin-top: 5px;
+ margin-bottom: 30px;
+ }
+}
+
+@media only screen and (max-width: 400px) {
+}
diff --git a/src/autoload.php b/src/autoload.php
new file mode 100644
index 00000000..7e9a2834
--- /dev/null
+++ b/src/autoload.php
@@ -0,0 +1,12 @@
+=3 <3.2.2"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.6.8",
+ "doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpspec/prophecy": "^1.10",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ],
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "support": {
+ "issues": "https://github.com/myclabs/DeepCopy/issues",
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-06-12T14:39:25+00:00"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v5.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb",
+ "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "php": ">=7.4"
+ },
+ "require-dev": {
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^9.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/nikic/PHP-Parser/issues",
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.2.0"
+ },
+ "time": "2024-09-15T16:40:33+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-phar": "*",
+ "ext-xmlwriter": "*",
+ "phar-io/version": "^3.0.1",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "support": {
+ "issues": "https://github.com/phar-io/manifest/issues",
+ "source": "https://github.com/phar-io/manifest/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:33:53+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "support": {
+ "issues": "https://github.com/phar-io/version/issues",
+ "source": "https://github.com/phar-io/version/tree/3.2.1"
+ },
+ "time": "2022-02-21T01:04:05+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "11.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ebdffc9e09585dafa71b9bffcdb0a229d4704c45",
+ "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-xmlwriter": "*",
+ "nikic/php-parser": "^5.1.0",
+ "php": ">=8.2",
+ "phpunit/php-file-iterator": "^5.0.1",
+ "phpunit/php-text-template": "^4.0.1",
+ "sebastian/code-unit-reverse-lookup": "^4.0.1",
+ "sebastian/complexity": "^4.0.1",
+ "sebastian/environment": "^7.2.0",
+ "sebastian/lines-of-code": "^3.0.1",
+ "sebastian/version": "^5.0.1",
+ "theseer/tokenizer": "^1.2.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "suggest": {
+ "ext-pcov": "PHP extension that provides line coverage",
+ "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "11.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+ "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-08-22T04:37:56+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "5.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6",
+ "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+ "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-08-27T05:02:59+00:00"
+ },
+ {
+ "name": "phpunit/php-invoker",
+ "version": "5.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2",
+ "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^11.0"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "security": "https://github.com/sebastianbergmann/php-invoker/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:07:44+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964",
+ "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+ "security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:08:43+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "7.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3",
+ "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "security": "https://github.com/sebastianbergmann/php-timer/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:09:35+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "11.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "7875627f15f4da7e7f0823d1f323f7295a77334e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7875627f15f4da7e7f0823d1f323f7295a77334e",
+ "reference": "7875627f15f4da7e7f0823d1f323f7295a77334e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "ext-xmlwriter": "*",
+ "myclabs/deep-copy": "^1.12.0",
+ "phar-io/manifest": "^2.0.4",
+ "phar-io/version": "^3.2.1",
+ "php": ">=8.2",
+ "phpunit/php-code-coverage": "^11.0.6",
+ "phpunit/php-file-iterator": "^5.1.0",
+ "phpunit/php-invoker": "^5.0.1",
+ "phpunit/php-text-template": "^4.0.1",
+ "phpunit/php-timer": "^7.0.1",
+ "sebastian/cli-parser": "^3.0.2",
+ "sebastian/code-unit": "^3.0.1",
+ "sebastian/comparator": "^6.1.0",
+ "sebastian/diff": "^6.0.2",
+ "sebastian/environment": "^7.2.0",
+ "sebastian/exporter": "^6.1.3",
+ "sebastian/global-state": "^7.0.2",
+ "sebastian/object-enumerator": "^6.0.1",
+ "sebastian/type": "^5.1.0",
+ "sebastian/version": "^5.0.1"
+ },
+ "suggest": {
+ "ext-soap": "To be able to generate mocks based on WSDL files"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "11.4-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Framework/Assert/Functions.php"
+ ],
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+ "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/11.4.1"
+ },
+ "funding": [
+ {
+ "url": "https://phpunit.de/sponsors.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-10-08T15:38:37+00:00"
+ },
+ {
+ "name": "sebastian/cli-parser",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180",
+ "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for parsing CLI options",
+ "homepage": "https://github.com/sebastianbergmann/cli-parser",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+ "security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:41:36+00:00"
+ },
+ {
+ "name": "sebastian/code-unit",
+ "version": "3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit.git",
+ "reference": "6bb7d09d6623567178cf54126afa9c2310114268"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6bb7d09d6623567178cf54126afa9c2310114268",
+ "reference": "6bb7d09d6623567178cf54126afa9c2310114268",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/code-unit",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+ "security": "https://github.com/sebastianbergmann/code-unit/security/policy",
+ "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:44:28+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "183a9b2632194febd219bb9246eee421dad8d45e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e",
+ "reference": "183a9b2632194febd219bb9246eee421dad8d45e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+ "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy",
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:45:54+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "6.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "fa37b9e2ca618cb051d71b60120952ee8ca8b03d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa37b9e2ca618cb051d71b60120952ee8ca8b03d",
+ "reference": "fa37b9e2ca618cb051d71b60120952ee8ca8b03d",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-mbstring": "*",
+ "php": ">=8.2",
+ "sebastian/diff": "^6.0",
+ "sebastian/exporter": "^6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/comparator/issues",
+ "security": "https://github.com/sebastianbergmann/comparator/security/policy",
+ "source": "https://github.com/sebastianbergmann/comparator/tree/6.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-09-11T15:42:56+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "ee41d384ab1906c68852636b6de493846e13e5a0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0",
+ "reference": "ee41d384ab1906c68852636b6de493846e13e5a0",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for calculating the complexity of PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/complexity",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/complexity/issues",
+ "security": "https://github.com/sebastianbergmann/complexity/security/policy",
+ "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:49:50+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "6.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544",
+ "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0",
+ "symfony/process": "^4.2 || ^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/diff/issues",
+ "security": "https://github.com/sebastianbergmann/diff/security/policy",
+ "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:53:05+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5",
+ "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "https://github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/environment/issues",
+ "security": "https://github.com/sebastianbergmann/environment/security/policy",
+ "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:54:44+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "6.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e",
+ "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": ">=8.2",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "https://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/exporter/issues",
+ "security": "https://github.com/sebastianbergmann/exporter/security/policy",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/6.1.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:56:19+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "7.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "3be331570a721f9a4b5917f4209773de17f747d7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7",
+ "reference": "3be331570a721f9a4b5917f4209773de17f747d7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "sebastian/object-reflector": "^4.0",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "https://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/global-state/issues",
+ "security": "https://github.com/sebastianbergmann/global-state/security/policy",
+ "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:57:36+00:00"
+ },
+ {
+ "name": "sebastian/lines-of-code",
+ "version": "3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a",
+ "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for counting the lines of code in PHP source code",
+ "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+ "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:58:38+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "6.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "f5b498e631a74204185071eb41f33f38d64608aa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa",
+ "reference": "f5b498e631a74204185071eb41f33f38d64608aa",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "sebastian/object-reflector": "^4.0",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+ "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:00:13+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9",
+ "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+ "security": "https://github.com/sebastianbergmann/object-reflector/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:01:32+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "6.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "694d156164372abbd149a4b85ccda2e4670c0e16"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16",
+ "reference": "694d156164372abbd149a4b85ccda2e4670c0e16",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "https://github.com/sebastianbergmann/recursion-context",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+ "security": "https://github.com/sebastianbergmann/recursion-context/security/policy",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:10:34+00:00"
+ },
+ {
+ "name": "sebastian/type",
+ "version": "5.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac",
+ "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the types of the PHP type system",
+ "homepage": "https://github.com/sebastianbergmann/type",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/type/issues",
+ "security": "https://github.com/sebastianbergmann/type/security/policy",
+ "source": "https://github.com/sebastianbergmann/type/tree/5.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-09-17T13:12:04+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "5.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "45c9debb7d039ce9b97de2f749c2cf5832a06ac4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/45c9debb7d039ce9b97de2f749c2cf5832a06ac4",
+ "reference": "45c9debb7d039ce9b97de2f749c2cf5832a06ac4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "security": "https://github.com/sebastianbergmann/version/security/policy",
+ "source": "https://github.com/sebastianbergmann/version/tree/5.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:13:08+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+ "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "support": {
+ "issues": "https://github.com/theseer/tokenizer/issues",
+ "source": "https://github.com/theseer/tokenizer/tree/1.2.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:36:25+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": {},
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": "^8.2"
+ },
+ "platform-dev": {},
+ "plugin-api-version": "2.6.0"
+}
diff --git a/src/css/admin_style.css b/src/css/admin_style.css
new file mode 100644
index 00000000..19b60ed2
--- /dev/null
+++ b/src/css/admin_style.css
@@ -0,0 +1,558 @@
+@import url('https://fonts.googleapis.com/css2?family=Rubik:wght@300;400;500;600&display=swap');
+
+:root{
+ --purple:#8e44ad;
+ --red:#c0392b;
+ --orange:#f39c12;
+ --black:#333;
+ --white:#fff;
+ --light-color:#666;
+ --light-white:#ccc;
+ --light-bg:#f5f5f5;
+ --border:.1rem solid var(--black);
+ --box-shadow:0 .5rem 1rem rgba(0,0,0,.1);
+}
+
+*{
+ font-family: 'Rubik', sans-serif;
+ margin:0; padding:0;
+ box-sizing: border-box;
+ outline: none; border:none;
+ text-decoration: none;
+ transition:all .2s linear;
+}
+
+*::selection{
+ background-color: var(--purple);
+ color:var(--white);
+}
+
+*::-webkit-scrollbar{
+ height: .5rem;
+ width: 1rem;
+}
+
+*::-webkit-scrollbar-track{
+ background-color: transparent;
+}
+
+*::-webkit-scrollbar-thumb{
+ background-color: var(--purple);
+}
+
+html{
+ font-size: 62.5%;
+ overflow-x: hidden;
+}
+
+body{
+ background-color: var(--light-bg);
+}
+
+section{
+ padding:3rem 2rem;
+}
+
+.title{
+ text-align: center;
+ margin-bottom: 2rem;
+ text-transform: uppercase;
+ color:var(--black);
+ font-size: 4rem;
+}
+
+.empty{
+ padding:1.5rem;
+ text-align: center;
+ border:var(--border);
+ background-color: var(--white);
+ color:var(--red);
+ font-size: 2rem;
+}
+
+.message{
+ position: sticky;
+ top:0;
+ margin:0 auto;
+ max-width: 1200px;
+ background-color: var(--light-bg);
+ padding:2rem;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ z-index: 10000;
+ gap:1.5rem;
+}
+
+.message span{
+ font-size: 2rem;
+ color:var(--black);
+}
+
+.message i{
+ cursor: pointer;
+ color:var(--red);
+ font-size: 2.5rem;
+}
+
+.message i:hover{
+ transform: rotate(90deg);
+}
+
+.btn,
+.option-btn,
+.delete-btn,
+.white-btn{
+ display: inline-block;
+ margin-top: 1rem;
+ padding:1rem 3rem;
+ cursor: pointer;
+ color:var(--white);
+ font-size: 1.8rem;
+ border-radius: .5rem;
+ text-transform: capitalize;
+}
+
+.btn:hover,
+.option-btn:hover,
+.delete-btn:hover{
+ background-color: var(--black);
+}
+
+.white-btn,
+.btn{
+ background-color: var(--purple);
+}
+
+.option-btn{
+ background-color: var(--orange);
+}
+
+.delete-btn{
+ background-color: var(--red);
+}
+
+.white-btn:hover{
+ background-color: var(--white);
+ color:var(--black);
+}
+
+@keyframes fadeIn {
+ 0%{
+ transform: translateY(1rem);
+ opacity: .2s;
+ }
+}
+
+.header{
+ position: sticky;
+ top:0; left:0; right:0;
+ z-index: 1000;
+ background-color: var(--white);
+ box-shadow: var(--box-shadow);
+}
+
+.header .flex{
+ display: flex;
+ align-items: center;
+ padding:2rem;
+ justify-content: space-between;
+ position: relative;
+ max-width: 1200px;
+ margin:0 auto;
+}
+
+.header .flex .logo{
+ font-size: 2.5rem;
+ color:var(--black);
+}
+
+.header .flex .logo span{
+ color:var(--purple);
+}
+
+.header .flex .navbar a{
+ margin:0 1rem;
+ font-size: 2rem;
+ color:var(--black);
+}
+
+.header .flex .navbar a:hover{
+ color:var(--purple);
+}
+
+.header .flex .icons div{
+ margin-left: 1.5rem;
+ font-size: 2.5rem;
+ cursor: pointer;
+ color:var(--black);
+}
+
+.header .flex .icons div:hover{
+ color:var(--purple);
+}
+
+.header .flex .account-box{
+ position: absolute;
+ top:120%; right:2rem;
+ width: 30rem;
+ box-shadow: var(--box-shadow);
+ border-radius: .5rem;
+ padding:2rem;
+ text-align: center;
+ border:var(--border);
+ background-color: var(--white);
+ display: none;
+ animation:fadeIn .2s linear;
+}
+
+.header .flex .account-box.active{
+ display: inline-block;
+}
+
+.header .flex .account-box p{
+ font-size: 2rem;
+ color:var(--light-color);
+ margin-bottom: 1.5rem;
+}
+
+.header .flex .account-box p span{
+ color:var(--purple);
+}
+
+.header .flex .account-box .delete-btn{
+ margin-top: 0;
+}
+
+.header .flex .account-box div{
+ margin-top: 1.5rem;
+ font-size: 2rem;
+ color:var(--light-color);
+}
+
+.header .flex .account-box div a{
+ color:var(--orange);
+}
+
+.header .flex .account-box div a:hover{
+ text-decoration: underline;
+}
+
+#menu-btn{
+ display: none;
+}
+
+.dashboard .box-container{
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(25rem, 1fr));
+ gap:1.5rem;
+ max-width: 1200px;
+ margin:0 auto;
+ align-items: flex-start;
+}
+
+.dashboard .box-container .box{
+ border-radius: .5rem;
+ padding:2rem;
+ background-color: var(--white);
+ box-shadow: var(--box-shadow);
+ border:var(--border);
+ text-align: center;
+}
+
+.dashboard .box-container .box h3{
+ font-size: 5rem;
+ color:var(--black);
+}
+
+.dashboard .box-container .box p{
+ margin-top: 1.5rem;
+ padding:1.5rem;
+ background-color: var(--light-bg);
+ color:var(--purple);
+ font-size: 2rem;
+ border-radius: .5rem;
+ border:var(--border);
+}
+
+.add-products form{
+ background-color: var(--white);
+ border-radius: .5rem;
+ padding:2rem;
+ text-align: center;
+ box-shadow: var(--box-shadow);
+ border:var(--border);
+ max-width: 50rem;
+ margin:0 auto;
+}
+
+.add-products form h3{
+ font-size: 2.5rem;
+ text-transform: uppercase;
+ color:var(--black);
+ margin-bottom: 1.5rem;
+}
+
+.add-products form .box{
+ width: 100%;
+ background-color: var(--light-bg);
+ border-radius: .5rem;
+ margin:1rem 0;
+ padding:1.2rem 1.4rem;
+ color:var(--black);
+ font-size: 1.8rem;
+ border:var(--border);
+}
+
+.show-products .box-container{
+ display: grid;
+ grid-template-columns: repeat(auto-fit, 30rem);
+ justify-content: center;
+ gap:1.5rem;
+ max-width: 1200px;
+ margin:0 auto;
+ align-items: flex-start;
+}
+
+.show-products{
+ padding-top: 0;
+}
+
+.show-products .box-container .box{
+ text-align: center;
+ padding:2rem;
+ border-radius: .5rem;
+ border:var(--border);
+ box-shadow: var(--box-shadow);
+ background-color: var(--white);
+}
+
+.show-products .box-container .box img{
+ height: 30rem;
+}
+
+.show-products .box-container .box .name{
+ padding:1rem 0;
+ font-size: 2rem;
+ color:var(--black);
+}
+
+.show-products .box-container .box .price{
+ padding:1rem 0;
+ font-size: 2.5rem;
+ color:var(--red);
+}
+
+.edit-product-form{
+ min-height: 100vh;
+ background-color: rgba(0,0,0,.7);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding:2rem;
+ overflow-y: scroll;
+ position: fixed;
+ top:0; left:0;
+ z-index: 1200;
+ width: 100%;
+}
+
+.edit-product-form form{
+ width: 50rem;
+ padding:2rem;
+ text-align: center;
+ border-radius: .5rem;
+ background-color: var(--white);
+}
+
+.edit-product-form form img{
+ height: 25rem;
+ margin-bottom: 1rem;
+}
+
+.edit-product-form form .box{
+ margin:1rem 0;
+ padding:1.2rem 1.4rem;
+ border:var(--border);
+ border-radius: .5rem;
+ background-color: var(--light-bg);
+ font-size: 1.8rem;
+ color:var(--black);
+ width: 100%;
+}
+
+.orders .box-container{
+ display: grid;
+ grid-template-columns: repeat(auto-fit, 30rem);
+ justify-content: center;
+ gap:1.5rem;
+ max-width: 1200px;
+ margin:0 auto;
+ align-items: flex-start;
+}
+
+.orders .box-container .box{
+ background-color: var(--white);
+ padding:2rem;
+ border:var(--border);
+ box-shadow: var(--box-shadow);
+ border-radius: .5rem;
+}
+
+.orders .box-container .box p{
+ padding-bottom: 1rem;
+ font-size: 2rem;
+ color:var(--light-color);
+}
+
+.orders .box-container .box p span{
+ color:var(--purple);
+}
+
+.orders .box-container .box form{
+ text-align: center;
+}
+
+.orders .box-container .box form select{
+ border-radius: .5rem;
+ margin:.5rem 0;
+ width: 100%;
+ background-color: var(--light-bg);
+ border:var(--border);
+ padding:1.2rem 1.4rem;
+ font-size: 1.8rem;
+ color:var(--black);
+}
+
+.users .box-container{
+ display: grid;
+ grid-template-columns: repeat(auto-fit, 30rem);
+ justify-content: center;
+ gap:1.5rem;
+ max-width: 1200px;
+ margin:0 auto;
+ align-items: flex-start;
+}
+
+.users .box-container .box{
+ background-color: var(--white);
+ padding:2rem;
+ border:var(--border);
+ box-shadow: var(--box-shadow);
+ border-radius: .5rem;
+ text-align: center;
+}
+
+.users .box-container .box p{
+ padding-bottom: 1.5rem;
+ font-size: 2rem;
+ color:var(--light-color);
+}
+
+.users .box-container .box p span{
+ color:var(--purple);
+}
+
+.users .box-container .box .delete-btn{
+ margin-top: 0;
+}
+
+.messages .box-container{
+ display: grid;
+ grid-template-columns: repeat(auto-fit, 35rem);
+ justify-content: center;
+ gap:1.5rem;
+ max-width: 1200px;
+ margin:0 auto;
+ align-items: flex-start;
+}
+
+.messages .box-container .box{
+ background-color: var(--white);
+ padding:2rem;
+ border:var(--border);
+ box-shadow: var(--box-shadow);
+ border-radius: .5rem;
+}
+
+.messages .box-container .box p{
+ padding-bottom: 1.5rem;
+ font-size: 2rem;
+ color:var(--light-color);
+ line-height: 1.5;
+}
+
+.messages .box-container .box p span{
+ color:var(--purple);
+}
+
+.messages .box-container .box .delete-btn{
+ margin-top: 0;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/* media queries */
+
+@media (max-width:991px){
+
+ html{
+ font-size: 55%;
+ }
+
+}
+
+@media (max-width:768px){
+
+ #menu-btn{
+ display: inline-block;
+ }
+
+ .header .flex .navbar{
+ position: absolute;
+ top:99%; left:0; right:0;
+ background-color: var(--white);
+ border-top: var(--border);
+ border-bottom: var(--border);
+ clip-path: polygon(0 0, 100% 0, 100% 0, 0 0);
+ }
+
+ .header .flex .navbar.active{
+ clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
+ }
+
+ .header .flex .navbar a{
+ margin:2rem;
+ display: block;
+ }
+
+}
+
+@media (max-width:450px){
+
+ html{
+ font-size: 50%;
+ }
+
+ .title{
+ font-size: 3rem;
+ }
+
+}
\ No newline at end of file
diff --git a/src/css/style.css b/src/css/style.css
new file mode 100644
index 00000000..aa2f4948
--- /dev/null
+++ b/src/css/style.css
@@ -0,0 +1,978 @@
+@import url('https://fonts.googleapis.com/css2?family=Rubik:wght@300;400;500;600&display=swap');
+
+:root{
+ --purple:#ae5ad2;
+ --red:#c0392b;
+ --orange:#f39c12;
+ --black:#333;
+ --white:#fff;
+ --light-color:#737171;
+ --light-white:#ccc;
+ --light-bg:#f5f5f5;
+ --border:.1rem solid var(--black);
+ --box-shadow:0 .5rem 1rem rgba(0,0,0,.1);
+}
+
+*{
+ font-family: 'Rubik', sans-serif;
+ margin:0; padding:0;
+ box-sizing: border-box;
+ outline: none; border:none;
+ text-decoration: none;
+ transition:all .2s linear;
+}
+
+*::selection{
+ background-color: var(--purple);
+ color:var(--white);
+}
+
+*::-webkit-scrollbar{
+ height: .5rem;
+ width: 1rem;
+}
+
+*::-webkit-scrollbar-track{
+ background-color: transparent;
+}
+
+*::-webkit-scrollbar-thumb{
+ background-color: var(--purple);
+}
+
+html{
+ font-size: 62.5%;
+ overflow-x: hidden;
+}
+
+section{
+ padding:3rem 2rem;
+}
+
+.empty{
+ padding:1.5rem;
+ text-align: center;
+ border:var(--border);
+ background-color: var(--white);
+ color:var(--red);
+ font-size: 2rem;
+}
+
+.message{
+ position: sticky;
+ top:0;
+ margin:0 auto;
+ max-width: 1200px;
+ background-color: var(--white);
+ padding:2rem;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ z-index: 10000;
+ gap:1.5rem;
+}
+
+.message span{
+ font-size: 2rem;
+ color:var(--black);
+}
+
+.message i{
+ cursor: pointer;
+ color:var(--red);
+ font-size: 2.5rem;
+}
+
+.message i:hover{
+ transform: rotate(90deg);
+}
+
+.title{
+ text-align: center;
+ margin-bottom: 2rem;
+ text-transform: uppercase;
+ color:var(--black);
+ font-size: 4rem;
+}
+
+.btn,
+.option-btn,
+.delete-btn,
+.white-btn{
+ display: inline-block;
+ margin-top: 1rem;
+ padding:1rem 3rem;
+ cursor: pointer;
+ color:var(--white);
+ font-size: 1.8rem;
+ border-radius: .5rem;
+ text-transform: capitalize;
+}
+
+.btn:hover,
+.option-btn:hover,
+.delete-btn:hover{
+ background-color: var(--black);
+}
+
+.white-btn,
+.btn{
+ background-color: var(--purple);
+}
+
+.option-btn{
+ background-color: var(--orange);
+}
+
+.delete-btn{
+ background-color: var(--red);
+}
+
+.white-btn:hover{
+ background-color: var(--white);
+ color:var(--black);
+}
+
+.heading{
+ min-height: 30vh;
+ display: flex;
+ flex-flow: column;
+ align-items: center;
+ justify-content: center;
+ gap:1rem;
+ background: url(../images/heading-bg.webp) no-repeat;
+ background-size: cover;
+ background-position: center;
+ text-align: center;
+}
+
+.heading h3{
+ font-size: 5rem;
+ color:var(--white);
+ text-transform: uppercase;
+}
+
+.heading p{
+ font-size: 2.5rem;
+ color:var(--light-color);
+}
+
+.heading p a{
+ color:var(--purple);
+}
+
+.heading p a:hover{
+ text-decoration: underline;
+}
+
+@keyframes fadeIn {
+ 0%{
+ transform: translateY(1rem);
+ opacity: .2s;
+ }
+}
+
+.form-container{
+ min-height: 100vh;
+ background-color: var(--light-bg);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding:2rem;
+}
+
+.form-container form{
+ padding:2rem;
+ width: 50rem;
+ border-radius: .5rem;
+ box-shadow: var(--box-shadow);
+ border:var(--border);
+ background-color: var(--white);
+ text-align: center;
+}
+
+.form-container form h3{
+ font-size: 3rem;
+ margin-bottom: 1rem;
+ text-transform: uppercase;
+ color:var(--black);
+}
+
+.form-container form .box{
+ width: 100%;
+ border-radius: .5rem;
+ background-color: var(--light-bg);
+ padding:1.2rem 1.4rem;
+ font-size: 1.8rem;
+ color:var(--black);
+ border:var(--border);
+ margin:1rem 0;
+}
+
+.form-container form p{
+ padding-top: 1.5rem;
+ font-size: 2rem;
+ color:var(--black);
+}
+
+.form-container form p a{
+ color:var(--purple);
+}
+
+.form-container form p a:hover{
+ text-decoration: underline;
+}
+
+.header .header-1{
+ background-color: var(--light-bg);
+}
+
+.header .header-1 .flex{
+ padding:2rem;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ max-width: 1200px;
+ margin:0 auto;
+}
+
+.header .header-2{
+ background-color: var(--white);
+ box-shadow: var(--box-shadow);
+}
+
+.header .header-2.active{
+ position: fixed;
+ top:0; left:0; right:0;
+ z-index: 1000;
+}
+
+.header .header-2 .flex{
+ padding:2rem;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ max-width: 1200px;
+ margin:0 auto;
+ position: relative;
+}
+
+.header .header-1 .flex .share a{
+ font-size: 2.5rem;
+ margin-right: 1.5rem;
+ color:var(--black);
+}
+
+.header .header-1 .flex .share a:hover{
+ color:var(--purple);
+}
+
+.header .header-1 .flex p{
+ font-size: 2rem;
+ color:var(--light-color);
+}
+
+.header .header-1 .flex p a{
+ color:var(--purple);
+}
+
+.header .header-1 .flex p a:hover{
+ text-decoration: underline;
+}
+
+.header .header-2 .flex .logo{
+ font-size: 2.5rem;
+ color:var(--purple);
+}
+
+.header .header-2 .flex .navbar a{
+ margin:0 1rem;
+ font-size: 2rem;
+ color:var(--light-color);
+}
+
+.header .header-2 .flex .navbar a:hover{
+ color:var(--purple);
+ text-decoration: underline;
+}
+
+.header .header-2 .flex .icons > *{
+ font-size: 2.5rem;
+ color:var(--black);
+ cursor: pointer;
+ margin-left: 1.5rem;
+}
+
+.header .header-2 .flex .icons > *:hover{
+ color:var(--purple);
+}
+
+#menu-btn{
+ display: none;
+}
+
+.header .header-2 .flex .user-box{
+ position: absolute;
+ top:120%; right:2rem;
+ background-color: var(--white);
+ border-radius: .5rem;
+ box-shadow: var(--box-shadow);
+ border:var(--border);
+ padding:2rem;
+ text-align: center;
+ width: 30rem;
+ display: none;
+ animation: fadeIn .2s linear;
+}
+
+.header .header-2 .flex .user-box.active{
+ display: inline-block;
+}
+
+.header .header-2 .flex .user-box p{
+ font-size: 2rem;
+ color:var(--light-color);
+ margin-bottom: 1.5rem;
+}
+
+.header .header-2 .flex .user-box p span{
+ color:var(--purple);
+}
+
+.header .header-2 .flex .user-box .delete-btn{
+ margin-top: 0;
+}
+
+.home{
+ min-height: 70vh;
+ background:linear-gradient(rgba(0,0,0,.4), rgba(0,0,0,.1)), url(../images/home-bg.jpg) no-repeat;
+ background-size: cover;
+ background-position: center;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.home .content{
+ text-align: center;
+ width: 60rem;
+}
+
+.home .content h3{
+ font-size: 5.5rem;
+ color:var(--white);
+ text-transform: uppercase;
+}
+
+.home .content p{
+ font-size:1.8rem;
+ color:var(--light-white);
+ padding:1rem 0;
+ line-height: 1.5;
+}
+
+.products .box-container{
+ max-width: 1200px;
+ margin:0 auto;
+ display: grid;
+ grid-template-columns: repeat(auto-fit, 30rem);
+ align-items: flex-start;
+ gap:1.5rem;
+ justify-content: center;
+}
+
+.products .box-container .box{
+ border-radius: .5rem;
+ background-color: var(--white);
+ box-shadow: var(--box-shadow);
+ padding:2rem;
+ text-align: center;
+ border:var(--border);
+ position: relative;
+}
+
+.products .box-container .box .image{
+ height: 30rem;
+}
+
+.products .box-container .box .name{
+ padding:1rem 0;
+ font-size: 2rem;
+ color:var(--black);
+}
+
+.products .box-container .box .qty{
+ width: 100%;
+ padding:1.2rem 1.4rem;
+ border-radius: .5rem;
+ border:var(--border);
+ margin:1rem 0;
+ font-size: 2rem;
+}
+
+.products .box-container .box .price{
+ position: absolute;
+ top:1rem; left:1rem;
+ border-radius: .5rem;
+ padding:1rem;
+ font-size: 2.5rem;
+ color:var(--white);
+ background-color: var(--red);
+}
+
+.about .flex{
+ max-width: 1200px;
+ margin:0 auto;
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+.about .flex .image{
+ flex:1 1 40rem;
+}
+
+.about .flex .image img{
+ width: 100%;
+}
+
+.about .flex .content{
+ flex:1 1 40rem;
+ padding:2rem;
+ background-color: var(--light-bg);
+}
+
+.about .flex .content h3{
+ font-size: 3rem;
+ color:var(--black);
+ text-transform: uppercase;
+}
+
+.about .flex .content p{
+ padding:1rem 0;
+ line-height: 2;
+ font-size: 1.7rem;
+ color:var(--light-color);
+}
+
+.home-contact{
+ background-color: var(--light-color);
+}
+
+.home-contact .content{
+ max-width: 60rem;
+ text-align: center;
+ margin:0 auto;
+}
+
+.home-contact .content h3{
+ font-size: 3rem;
+ text-transform: uppercase;
+ color:var(--white);
+}
+
+.home-contact .content p{
+ padding:1rem 0;
+ line-height: 1.5;
+ color:var(--light-white);
+ font-size: 1.7rem;
+}
+
+.reviews{
+ background-color: var(--light-bg);
+}
+
+.reviews .box-container{
+ max-width: 1200px;
+ margin:0 auto;
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(30rem, 1fr));
+ align-items: center;
+ gap:1.5rem;
+ justify-content: center;
+}
+
+.reviews .box-container .box{
+ background-color: var(--white);
+ box-shadow: var(--box-shadow);
+ border:var(--border);
+ border-radius: .5rem;
+ text-align: center;
+ padding:2rem;
+}
+
+.reviews .box-container .box img{
+ height: 10rem;
+ width: 10rem;
+ border-radius: 50%;
+}
+
+.reviews .box-container .box p{
+ padding:1rem 0;
+ line-height: 2;
+ color:var(--light-color);
+ font-size: 1.5rem;
+}
+
+.reviews .box-container .box .stars{
+ background-color: var(--light-bg);
+ display: inline-block;
+ margin:.5rem 0;
+ border-radius: .5rem;
+ border:var(--border);
+ padding:.5rem 1.5rem;
+}
+
+.reviews .box-container .box .stars i{
+ font-size: 1.7rem;
+ color:var(--orange);
+ margin:.2rem;
+}
+
+.reviews .box-container .box h3{
+ font-size: 2rem;
+ color:var(--black);
+ margin-top: 1rem;
+}
+
+.authors .box-container{
+ max-width: 1200px;
+ margin:0 auto;
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(30rem, 1fr));
+ align-items: center;
+ gap:1.5rem;
+ justify-content: center;
+}
+
+.authors .box-container .box{
+ position: relative;
+ text-align: center;
+ border:var(--border);
+ box-shadow: var(--box-shadow);
+ overflow: hidden;
+ border-radius: .5rem;
+}
+
+.authors .box-container .box img{
+ width: 100%;
+ height: 40rem;
+ object-fit: cover;
+}
+
+.authors .box-container .box .share{
+ position: absolute;
+ top:0; left:-10rem;
+}
+
+.authors .box-container .box:hover .share{
+ left: 1rem;
+}
+
+.authors .box-container .box .share a{
+ height: 4.5rem;
+ width: 4.5rem;
+ line-height: 4.5rem;
+ font-size: 2rem;
+ background-color: var(--white);
+ border:var(--border);
+ display: block;
+ margin-top: 1rem;
+ color:var(--black);
+}
+
+.authors .box-container .box .share a:hover{
+ background-color: var(--black);
+ color:var(--white);
+}
+
+.authors .box-container .box h3{
+ font-size: 2.5rem;
+ color:var(--black);
+ padding:1.5rem;
+ background-color: var(--white);
+}
+
+.contact form{
+ margin:0 auto;
+ background-color: var(--light-bg);
+ border-radius: .5rem;
+ border:var(--border);
+ padding:2rem;
+ max-width: 50rem;
+ text-align: center;
+}
+
+.contact form h3{
+ font-size: 2.5rem;
+ text-transform: uppercase;
+ margin-bottom: 1rem;
+ color:var(--black);
+}
+
+.contact form .box{
+ margin:1rem 0;
+ width: 100%;
+ border:var(--border);
+ background-color: var(--white);
+ padding:1.2rem 1.4rem;
+ font-size: 1.8rem;
+ color:var(--black);
+ border-radius: .5rem;
+}
+
+.contact form textarea{
+ height: 20rem;
+ resize: none;
+}
+
+.shopping-cart .box-container{
+ max-width: 1200px;
+ margin:0 auto;
+ display: grid;
+ grid-template-columns: repeat(auto-fit, 30rem);
+ align-items: center;
+ gap:1.5rem;
+ justify-content: center;
+}
+
+.shopping-cart .box-container .box{
+ text-align: center;
+ padding:2rem;
+ border-radius: .5rem;
+ background-color: var(--white);
+ box-shadow: var(--box-shadow);
+ position: relative;
+ border:var(--border);
+}
+
+.shopping-cart .box-container .box .fa-times{
+ position: absolute;
+ top:1rem; right:1rem;
+ height: 4.5rem;
+ width: 4.5rem;
+ line-height: 4.5rem;
+ font-size: 2rem;
+ background-color: var(--red);
+ color:var(--white);
+ border-radius: .5rem;
+}
+
+.shopping-cart .box-container .box .fa-times:hover{
+ background-color: var(--black);
+}
+
+.shopping-cart .box-container .box img{
+ height: 30rem;
+}
+
+.shopping-cart .box-container .box .name{
+ padding:1rem 0;
+ font-size: 2rem;
+ color:var(--black);
+}
+
+.shopping-cart .box-container .box .price{
+ padding:1rem 0;
+ font-size: 2.5rem;
+ color:var(--red);
+}
+
+.shopping-cart .box-container .box input[type="number"]{
+ margin:.5rem 0;
+ border:var(--border);
+ border-radius: .5rem;
+ padding:1.2rem 1.4rem;
+ font-size: 2rem;
+ color:var(--black);
+ width: 9rem;
+}
+
+.shopping-cart .box-container .box .sub-total{
+ padding-top: 1.5rem;
+ font-size: 2rem;
+ color:var(--light-color);
+}
+
+.shopping-cart .box-container .box .sub-total span{
+ color:var(--red);
+}
+
+.shopping-cart .cart-total{
+ max-width: 1200px;
+ margin:0 auto;
+ border:var(--border);
+ padding:2rem;
+ text-align: center;
+ margin-top: 2rem;
+ border-radius: .5rem;
+}
+
+.shopping-cart .cart-total p{
+ font-size: 2.5rem;
+ color:var(--light-color);
+}
+
+.shopping-cart .cart-total p span{
+ color:var(--red);
+}
+
+.shopping-cart .cart-total .flex{
+ display: flex;
+ flex-wrap: wrap;
+ column-gap:1rem;
+ margin-top: 1.5rem;
+ justify-content: center;
+}
+
+.shopping-cart .disabled{
+ pointer-events: none;
+ opacity: .5;
+ user-select: none;
+}
+
+.display-order{
+ max-width: 1200px;
+ margin: 0 auto;
+ text-align: center;
+ padding-bottom: 0;
+}
+
+.display-order p{
+ background-color: var(--light-bg);
+ color:var(--black);
+ font-size: 2rem;
+ padding:1rem 1.5rem;
+ border:var(--border);
+ display: inline-block;
+ margin:.5rem;
+}
+
+.display-order p span{
+ color:var(--red);
+}
+
+.display-order .grand-total{
+ margin-top: 2rem;
+ font-size: 2.5rem;
+ color:var(--light-color);
+}
+
+.display-order .grand-total span{
+ color:var(--red);
+}
+
+.checkout form{
+ max-width: 1200px;
+ padding:2rem;
+ margin:0 auto;
+ border:var(--border);
+ background-color: var(--light-bg);
+ border-radius: .5rem;
+}
+
+.checkout form h3{
+ text-align: center;
+ margin-bottom: 2rem;
+ color:var(--black);
+ text-transform: uppercase;
+ font-size: 3rem;
+}
+
+.checkout form .flex{
+ display: flex;
+ flex-wrap: wrap;
+ gap:1.5rem;
+}
+
+.checkout form .flex .inputBox{
+ flex:1 1 40rem;
+}
+
+.checkout form .flex span{
+ font-size: 2rem;
+ color:var(--black);
+}
+
+.checkout form .flex select,
+.checkout form .flex input{
+ border:var(--border);
+ width: 100%;
+ border-radius: .5rem;
+ background-color: var(--white);
+ padding:1.2rem 1.4rem;
+ font-size: 1.8rem;
+ margin:1rem 0;
+}
+
+.placed-orders .box-container{
+ max-width: 1200px;
+ margin:0 auto;
+ display:flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap:1.5rem;
+}
+
+.placed-orders .box-container .empty{
+ flex:1;
+}
+
+.placed-orders .box-container .box{
+ flex:1 1 40rem;
+ border-radius: .5rem;
+ padding:2rem;
+ border:var(--border);
+ background-color: var(--light-bg);
+}
+
+.placed-orders .box-container .box p{
+ padding:1rem 0;
+ font-size: 2rem;
+ color:var(--light-color);
+}
+
+.placed-orders .box-container .box p span{
+ color:var(--purple);
+}
+
+.search-form form{
+ max-width: 1200px;
+ margin:0 auto;
+ display: flex;
+ gap:1rem;
+}
+
+.search-form form .btn{
+ margin-top: 0;
+}
+
+.search-form form .box{
+ width: 100%;
+ padding:1.2rem 1.4rem;
+ border:var(--border);
+ font-size: 2rem;
+ color:var(--black);
+ background-color: var(--light-bg);
+ border-radius: .5rem;
+}
+
+
+
+
+
+
+
+
+
+
+.footer{
+ background-color: var(--light-bg);
+}
+
+.footer .box-container{
+ max-width: 1200px;
+ margin:0 auto;
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(25rem, 1fr));
+ gap:3rem;
+}
+
+.footer .box-container .box h3{
+ text-transform: uppercase;
+ color:var(--black);
+ font-size: 2rem;
+ padding-bottom: 2rem;
+}
+
+.footer .box-container .box p,
+.footer .box-container .box a{
+ display: block;
+ font-size: 1.7rem;
+ color:var(--light-color);
+ padding:1rem 0;
+}
+
+.footer .box-container .box p i,
+.footer .box-container .box a i{
+ color:var(--purple);
+ padding-right: .5rem;
+}
+
+.footer .box-container .box a:hover{
+ color:var(--purple);
+ text-decoration: underline;
+}
+
+.footer .credit{
+ text-align: center;
+ font-size: 2rem;
+ color:var(--light-color);
+ border-top: var(--border);
+ margin-top: 2.5rem;
+ padding-top: 2.5rem;
+}
+
+.footer .credit span{
+ color:var(--purple);
+}
+
+
+
+
+/* media queries */
+
+@media (max-width:991px){
+
+ html{
+ font-size: 55%;
+ }
+
+}
+
+@media (max-width:768px){
+
+ #menu-btn{
+ display: inline-block;
+ }
+
+ .header .header-2 .flex .navbar{
+ position: absolute;
+ top:99%; left:0; right:0;
+ background-color: var(--white);
+ border-top: var(--border);
+ border-bottom: var(--border);
+ clip-path: polygon(0 0, 100% 0, 100% 0, 0 0);
+ }
+
+ .header .header-2 .flex .navbar.active{
+ clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
+ }
+
+ .header .header-2 .flex .navbar a{
+ display: block;
+ font-size: 2.5rem;
+ margin:2rem;
+ }
+
+ .home .content h3{
+ font-size: 3.5rem;
+ }
+
+}
+
+@media (max-width:450px){
+
+ html{
+ font-size: 50%;
+ }
+
+ .heading h3{
+ font-size: 3.5rem;
+ }
+
+ .title{
+ font-size: 3rem;
+ }
+
+}
\ No newline at end of file
diff --git a/src/images/about-img.jpg b/src/images/about-img.jpg
new file mode 100644
index 00000000..ab74d1b4
Binary files /dev/null and b/src/images/about-img.jpg differ
diff --git a/src/images/author-1.jpg b/src/images/author-1.jpg
new file mode 100644
index 00000000..1b8cbf4d
Binary files /dev/null and b/src/images/author-1.jpg differ
diff --git a/src/images/author-2.jpg b/src/images/author-2.jpg
new file mode 100644
index 00000000..c99e019a
Binary files /dev/null and b/src/images/author-2.jpg differ
diff --git a/src/images/author-3.jpg b/src/images/author-3.jpg
new file mode 100644
index 00000000..31725509
Binary files /dev/null and b/src/images/author-3.jpg differ
diff --git a/src/images/author-4.jpg b/src/images/author-4.jpg
new file mode 100644
index 00000000..905182c6
Binary files /dev/null and b/src/images/author-4.jpg differ
diff --git a/src/images/author-5.jpg b/src/images/author-5.jpg
new file mode 100644
index 00000000..c34296ca
Binary files /dev/null and b/src/images/author-5.jpg differ
diff --git a/src/images/author-6.jpg b/src/images/author-6.jpg
new file mode 100644
index 00000000..9ff593bb
Binary files /dev/null and b/src/images/author-6.jpg differ
diff --git a/src/images/heading-bg.webp b/src/images/heading-bg.webp
new file mode 100644
index 00000000..5ad8e92d
Binary files /dev/null and b/src/images/heading-bg.webp differ
diff --git a/src/images/home-bg.jpg b/src/images/home-bg.jpg
new file mode 100644
index 00000000..5ad8e92d
Binary files /dev/null and b/src/images/home-bg.jpg differ
diff --git a/src/images/pic-1.png b/src/images/pic-1.png
new file mode 100644
index 00000000..9eaa902a
Binary files /dev/null and b/src/images/pic-1.png differ
diff --git a/src/images/pic-2.png b/src/images/pic-2.png
new file mode 100644
index 00000000..b3254f56
Binary files /dev/null and b/src/images/pic-2.png differ
diff --git a/src/images/pic-3.png b/src/images/pic-3.png
new file mode 100644
index 00000000..1040f92d
Binary files /dev/null and b/src/images/pic-3.png differ
diff --git a/src/images/pic-4.png b/src/images/pic-4.png
new file mode 100644
index 00000000..00ef2d59
Binary files /dev/null and b/src/images/pic-4.png differ
diff --git a/src/images/pic-5.png b/src/images/pic-5.png
new file mode 100644
index 00000000..2f9c5704
Binary files /dev/null and b/src/images/pic-5.png differ
diff --git a/src/images/pic-6.png b/src/images/pic-6.png
new file mode 100644
index 00000000..dcf83c65
Binary files /dev/null and b/src/images/pic-6.png differ
diff --git a/src/index.php b/src/index.php
new file mode 100644
index 00000000..78b302c7
--- /dev/null
+++ b/src/index.php
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/src/js/admin_script.js b/src/js/admin_script.js
new file mode 100644
index 00000000..80d79ab5
--- /dev/null
+++ b/src/js/admin_script.js
@@ -0,0 +1,22 @@
+let navbar = document.querySelector('.header .navbar');
+let accountBox = document.querySelector('.header .account-box');
+
+document.querySelector('#menu-btn').onclick = () =>{
+ navbar.classList.toggle('active');
+ accountBox.classList.remove('active');
+}
+
+document.querySelector('#user-btn').onclick = () =>{
+ accountBox.classList.toggle('active');
+ navbar.classList.remove('active');
+}
+
+window.onscroll = () =>{
+ navbar.classList.remove('active');
+ accountBox.classList.remove('active');
+}
+
+document.querySelector('#close-update').onclick = () =>{
+ document.querySelector('.edit-product-form').style.display = 'none';
+ window.location.href = 'admin_products.php';
+}
\ No newline at end of file
diff --git a/src/js/script.js b/src/js/script.js
new file mode 100644
index 00000000..00da8f88
--- /dev/null
+++ b/src/js/script.js
@@ -0,0 +1,24 @@
+let userBox = document.querySelector('.header .header-2 .user-box');
+
+document.querySelector('#user-btn').onclick = () =>{
+ userBox.classList.toggle('active');
+ navbar.classList.remove('active');
+}
+
+let navbar = document.querySelector('.header .header-2 .navbar');
+
+document.querySelector('#menu-btn').onclick = () =>{
+ navbar.classList.toggle('active');
+ userBox.classList.remove('active');
+}
+
+window.onscroll = () =>{
+ userBox.classList.remove('active');
+ navbar.classList.remove('active');
+
+ if(window.scrollY > 60){
+ document.querySelector('.header .header-2').classList.add('active');
+ }else{
+ document.querySelector('.header .header-2').classList.remove('active');
+ }
+}
\ No newline at end of file
diff --git a/src/uploaded_img/67454f671e08a.jpg b/src/uploaded_img/67454f671e08a.jpg
new file mode 100644
index 00000000..41dec8d7
Binary files /dev/null and b/src/uploaded_img/67454f671e08a.jpg differ
diff --git a/src/uploaded_img/author-2.jpg b/src/uploaded_img/author-2.jpg
new file mode 100644
index 00000000..c99e019a
Binary files /dev/null and b/src/uploaded_img/author-2.jpg differ
diff --git a/tests/BDD/features/admin.feature b/tests/BDD/features/admin.feature
new file mode 100644
index 00000000..849c9a8a
--- /dev/null
+++ b/tests/BDD/features/admin.feature
@@ -0,0 +1,51 @@
+# language: es
+Característica: Panel de Administración
+ Como administrador del sistema
+ Quiero gestionar productos y usuarios
+ Para mantener la tienda actualizada
+
+ Antecedentes:
+ Dado que estoy logueado como administrador
+ Y estoy en el panel de administración
+
+ Escenario: Ver dashboard
+ Entonces debería ver estadísticas de:
+ | Ventas totales |
+ | Nuevos usuarios |
+ | Productos activos |
+ | Pedidos pendientes|
+
+ Escenario: Crear nuevo producto
+ Cuando accedo a "productos/nuevo"
+ Y completo los datos del producto:
+ | nombre | Producto Test |
+ | precio | 99.99 |
+ | stock | 100 |
+ | descripción | Descripción test |
+ Y presiono "Guardar"
+ Entonces debería ver "Producto creado correctamente"
+
+ Escenario: Editar producto existente
+ Cuando selecciono un producto existente
+ Y modifico el precio a "149.99"
+ Y presiono "Actualizar"
+ Entonces debería ver "Producto actualizado correctamente"
+
+ Escenario: Eliminar producto
+ Cuando selecciono un producto existente
+ Y presiono "Eliminar"
+ Y confirmo la acción
+ Entonces debería ver "Producto eliminado correctamente"
+
+ Escenario: Ver listado de usuarios
+ Cuando accedo a "usuarios"
+ Entonces debería ver la lista de usuarios registrados
+
+ Escenario: Ver pedidos recientes
+ Cuando accedo a "pedidos"
+ Entonces debería ver los últimos pedidos
+
+ Escenario: Cambiar estado de pedido
+ Cuando accedo a "pedidos"
+ Y selecciono un pedido
+ Y cambio el estado a "Enviado"
\ No newline at end of file
diff --git a/tests/BDD/features/auth.feature b/tests/BDD/features/auth.feature
new file mode 100644
index 00000000..b89181b6
--- /dev/null
+++ b/tests/BDD/features/auth.feature
@@ -0,0 +1,42 @@
+# language: es
+Característica: Autenticación de usuarios
+ Como usuario del sistema
+ Quiero poder registrarme y acceder a mi cuenta
+ Para gestionar mis compras y datos personales
+
+ Escenario: Registro exitoso
+ Dado que estoy en la página de registro
+ Cuando completo el formulario con:
+ | nombre | Juan Pérez |
+ | email | juan@email.com |
+ | contraseña | password123 |
+ | confirmar | password123 |
+ Y presiono "Registrar"
+ Entonces debería ver "Registro exitoso"
+
+ Escenario: Login exitoso
+ Dado que estoy en la página de login
+ Cuando ingreso "usuario@test.com" como email
+ Y ingreso "password123" como contraseña
+ Y presiono "Iniciar sesión"
+ Entonces debería estar logueado
+ Y debería ver "Bienvenido"
+
+ Escenario: Login fallido
+ Dado que estoy en la página de login
+ Cuando ingreso "usuario@test.com" como email
+ Y ingreso "wrongpassword" como contraseña
+ Y presiono "Iniciar sesión"
+ Entonces debería ver "Credenciales inválidas"
+
+ Escenario: Recuperar contraseña
+ Dado que estoy en la página de recuperación
+ Cuando ingreso "usuario@test.com" como email
+ Y presiono "Recuperar contraseña"
+ Entonces debería ver "Email de recuperación enviado"
+
+ Escenario: Cerrar sesión
+ Dado que estoy logueado
+ Cuando presiono "Cerrar sesión"
+ Entonces debería estar deslogueado
+ Y debería ver "Sesión cerrada correctamente"
\ No newline at end of file
diff --git a/tests/BDD/features/bootstrap/AdminContext.php b/tests/BDD/features/bootstrap/AdminContext.php
new file mode 100644
index 00000000..f4f66458
--- /dev/null
+++ b/tests/BDD/features/bootstrap/AdminContext.php
@@ -0,0 +1,164 @@
+user = [
+ 'email' => 'admin@test.com',
+ 'role' => 'admin'
+ ];
+ }
+
+ /**
+ * @Given estoy en el panel de administración
+ */
+ public function estoyEnElPanelDeAdministracion()
+ {
+ $this->currentPage = 'admin/dashboard';
+ $this->initializeTestData();
+ }
+
+ /**
+ * @Then debería ver estadísticas de:
+ */
+ public function deberiaVerEstadisticasDe(TableNode $table)
+ {
+ foreach ($table->getRows() as $row) {
+ Assert::assertArrayHasKey(
+ strtolower(str_replace(' ', '_', $row[0])),
+ $this->statistics
+ );
+ }
+ }
+
+ /**
+ * @When accedo a :page
+ */
+ public function accedoA($page)
+ {
+ $this->currentPage = "admin/$page";
+ }
+
+ /**
+ * @When completo los datos del producto:
+ */
+ public function completoLosDatosDelProducto(TableNode $table)
+ {
+ $this->selectedProduct = $table->getRowsHash();
+ }
+
+ /**
+ * @When selecciono un producto existente
+ */
+ public function seleccionoUnProductoExistente()
+ {
+ $this->selectedProduct = $this->products[0];
+ }
+
+ /**
+ * @When modifico el precio a :price
+ */
+ public function modificoElPrecioA($price)
+ {
+ $this->selectedProduct['precio'] = $price;
+ }
+
+ /**
+ * @When confirmo la acción
+ */
+ public function confirmoLaAccion()
+ {
+ // Simulación de confirmación
+ return true;
+ }
+
+ /**
+ * @Then debería ver la lista de usuarios registrados
+ */
+ public function deberiaVerLaListaDeUsuariosRegistrados()
+ {
+ Assert::assertNotEmpty($this->users);
+ }
+
+ /**
+ * @Then debería ver los últimos pedidos
+ */
+ public function deberiaVerLosUltimosPedidos()
+ {
+ Assert::assertNotEmpty($this->orders);
+ }
+
+ /**
+ * @When selecciono un pedido
+ */
+ public function seleccionoUnPedido()
+ {
+ $this->selectedOrder = $this->orders[0];
+ }
+
+ /**
+ * @When cambio el estado a :status
+ */
+ public function cambioElEstadoA($status)
+ {
+ $this->selectedOrder['estado'] = $status;
+ $this->lastMessage = 'Estado actualizado correctamente';
+ }
+
+ private function initializeTestData()
+ {
+ $this->statistics = [
+ 'ventas_totales' => 1500,
+ 'nuevos_usuarios' => 25,
+ 'productos_activos' => 100,
+ 'pedidos_pendientes' => 10
+ ];
+
+ $this->products = [
+ [
+ 'id' => 1,
+ 'nombre' => 'Producto Test',
+ 'precio' => 99.99,
+ 'stock' => 100
+ ]
+ ];
+
+ $this->users = [
+ [
+ 'email' => 'usuario@test.com',
+ 'nombre' => 'Usuario Test',
+ 'rol' => 'user'
+ ]
+ ];
+
+ $this->orders = [
+ [
+ 'id' => 1,
+ 'cliente' => 'Cliente Test',
+ 'total' => 99.99,
+ 'estado' => 'Pendiente',
+ 'fecha' => '2024-03-20'
+ ]
+ ];
+ }
+}
\ No newline at end of file
diff --git a/tests/BDD/features/bootstrap/AuthContext.php b/tests/BDD/features/bootstrap/AuthContext.php
new file mode 100644
index 00000000..e7758a73
--- /dev/null
+++ b/tests/BDD/features/bootstrap/AuthContext.php
@@ -0,0 +1,139 @@
+currentPage = $page;
+ }
+
+ /**
+ * @When completo el formulario con:
+ */
+ public function completoElFormularioCon(TableNode $table)
+ {
+ $this->formData = $table->getRowsHash();
+ }
+
+ /**
+ * @When ingreso :value como :field
+ */
+ public function ingresoValueComoField($value, $field)
+ {
+ $this->formData[$field] = $value;
+ }
+
+ /**
+ * @When presiono :button
+ */
+ public function ejecutarAccion($button)
+ {
+ switch ($button) {
+ case 'Registrar':
+ $this->lastMessage = 'Registro exitoso';
+ break;
+ case 'Iniciar sesión':
+ $this->procesarLogin();
+ break;
+ case 'Cerrar sesión':
+ $this->isLoggedIn = false;
+ $this->lastMessage = 'Sesión cerrada correctamente';
+ break;
+ case 'Recuperar contraseña':
+ $this->lastMessage = 'Email de recuperación enviado';
+ break;
+ case 'Guardar':
+ $this->lastMessage = 'Producto creado correctamente';
+ break;
+ case 'Actualizar':
+ $this->lastMessage = 'Producto actualizado correctamente';
+ break;
+ case 'Eliminar':
+ $this->lastMessage = 'Producto eliminado correctamente';
+ break;
+ case 'Compartir':
+ $this->lastMessage = 'Opciones de compartir disponibles';
+ break;
+ default:
+ $this->lastMessage = '';
+ }
+ }
+
+ /**
+ * @Then debería ver :message
+ */
+ public function verificarMensaje($message)
+ {
+ Assert::assertEquals(
+ $message,
+ $this->getUserContext()->getLastMessage()
+ );
+ }
+
+ /**
+ * @Then debería estar logueado
+ */
+ public function deberiaEstarLogueado()
+ {
+ Assert::assertTrue($this->isLoggedIn);
+ }
+
+ /**
+ * @Then debería estar deslogueado
+ */
+ public function deberiaEstarDeslogueado()
+ {
+ Assert::assertFalse($this->isLoggedIn);
+ }
+
+ /**
+ * @Given que estoy logueado
+ */
+ public function queEstoyLogueado()
+ {
+ $this->isLoggedIn = true;
+ $this->user = [
+ 'email' => 'usuario@test.com',
+ 'role' => 'user'
+ ];
+ }
+
+ private function procesarLogin()
+ {
+ if (
+ $this->formData['email'] === 'usuario@test.com' &&
+ $this->formData['contraseña'] === 'password123'
+ ) {
+ $this->isLoggedIn = true;
+ $this->lastMessage = 'Bienvenido';
+ } else {
+ $this->lastMessage = 'Credenciales inválidas';
+ }
+ }
+
+ private function getUserContext()
+ {
+ return $this;
+ }
+
+ public function getLastMessage()
+ {
+ return $this->lastMessage;
+ }
+}
\ No newline at end of file
diff --git a/tests/BDD/features/bootstrap/FeatureContext.php b/tests/BDD/features/bootstrap/FeatureContext.php
new file mode 100644
index 00000000..dfe14447
--- /dev/null
+++ b/tests/BDD/features/bootstrap/FeatureContext.php
@@ -0,0 +1,10 @@
+user = [
+ 'email' => 'usuario@test.com',
+ 'role' => 'user'
+ ];
+ }
+
+ /**
+ * @Given estoy en la página principal
+ */
+ public function estoyEnLaPaginaPrincipal()
+ {
+ $this->currentPage = 'home';
+ }
+
+ /**
+ * @Then debería ver la sección de últimos productos
+ */
+ public function deberiaVerLaSeccionDeUltimosProductos()
+ {
+ // Simulación
+ }
+
+ /**
+ * @Then debería ver la sección :section
+ */
+ public function deberiaVerLaSeccion($section)
+ {
+ // Simulación
+ }
+
+ /**
+ * @When busco el término :term
+ */
+ public function buscoElTermino($term)
+ {
+ $this->searchResults = ($term === 'xyzabc123') ? [] : ['Producto 1', 'Producto 2'];
+ $this->lastMessage = empty($this->searchResults) ?
+ '¡No se han encontrado resultados!' :
+ 'Resultados encontrados';
+ }
+
+ /**
+ * @Then debería ver productos relacionados con :term
+ */
+ public function deberiaVerProductosRelacionadosCon($term)
+ {
+ Assert::assertNotEmpty($this->searchResults);
+ }
+
+ /**
+ * @Given que estoy en la tienda
+ */
+ public function queEstoyEnLaTienda()
+ {
+ $this->currentPage = 'shop';
+ }
+
+ /**
+ * @When selecciono un producto
+ */
+ public function seleccionoUnProducto()
+ {
+ $this->selectedProduct = [
+ 'id' => 1,
+ 'nombre' => 'Producto Test',
+ 'precio' => 99.99
+ ];
+ $_SESSION['message'] = 'Producto agregado al carrito';
+ $this->lastMessage = $_SESSION['message'];
+ }
+
+ /**
+ * @When establezco cantidad :quantity
+ */
+ public function establezoCantidad($quantity)
+ {
+ $this->selectedProduct['cantidad'] = (int)$quantity;
+ }
+
+ /**
+ * @Given que tengo productos en el carrito
+ */
+ public function queTegoProductosEnElCarrito()
+ {
+ $this->cart = [
+ [
+ 'id' => 1,
+ 'nombre' => 'Producto 1',
+ 'precio' => 99.99,
+ 'cantidad' => 2
+ ]
+ ];
+ }
+
+ /**
+ * @When accedo al checkout
+ */
+ public function accedoAlCheckout()
+ {
+ $this->currentPage = 'checkout';
+ }
+
+ /**
+ * @When completo los datos de envío:
+ */
+ public function completoLosDatosDeEnvio(TableNode $table)
+ {
+ $this->checkoutData = $table->getRowsHash();
+ }
+
+ /**
+ * @When selecciono método de pago :method
+ */
+ public function seleccionoMetodoDePago($method)
+ {
+ $this->checkoutData['metodoPago'] = $method;
+ }
+
+ /**
+ * @Then debería poder finalizar la compra
+ */
+ public function deberiaPoderFinalizarLaCompra()
+ {
+ Assert::assertNotEmpty($this->checkoutData);
+ Assert::assertNotEmpty($this->cart);
+ }
+
+ /**
+ * @When no tengo productos en el carrito
+ */
+ public function noTengoProductosEnElCarrito()
+ {
+ $this->cart = [];
+ $this->lastMessage = 'Tu carrito está vacío';
+ $_SESSION['message'] = $this->lastMessage;
+ }
+
+ /**
+ * @When actualizo la cantidad de un producto
+ */
+ public function actualizoLaCantidadDeUnProducto()
+ {
+ if (!empty($this->cart)) {
+ $this->cart[0]['cantidad'] = 3;
+ }
+ }
+
+ /**
+ * @Then el total debería actualizarse
+ */
+ public function elTotalDeberiaActualizarse()
+ {
+ // Simulación
+ }
+
+ /**
+ * @Then debería ver el nuevo subtotal
+ */
+ public function deberiaVerElNuevoSubtotal()
+ {
+ // Simulación
+ }
+
+ /**
+ * @When elimino un producto
+ */
+ public function eliminoUnProducto()
+ {
+ array_pop($this->cart);
+ $_SESSION['message'] = 'Producto eliminado del carrito';
+ $this->lastMessage = $_SESSION['message'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getLastMessage(): string
+ {
+ return $this->lastMessage;
+ }
+
+ /**
+ * @Then debería ver el mensaje :message
+ */
+ public function deberiaVerElMensaje($message)
+ {
+ Assert::assertEquals($message, $this->lastMessage);
+ }
+
+
+ /**
+ * @When intento hacer checkout
+ */
+ public function intentoHacerCheckout()
+ {
+ // Implementar la lógica para iniciar el checkout
+ }
+
+ /**
+ * @When no completo todos los campos requeridos
+ */
+ public function noCompletoTodosLosCamposRequeridos()
+ {
+ // Implementar la lógica para simular campos incompletos
+ }
+
+ /**
+ * @Then debería ver mensajes de validación
+ */
+ public function deberiaVerMensajesDeValidacion()
+ {
+ // Verificar que se muestran mensajes de error
+ }
+
+ /**
+ * @When selecciono un producto específico
+ */
+ public function seleccionoUnProductoEspecifico()
+ {
+ $this->selectedProduct = [
+ 'id' => 1,
+ 'nombre completo' => 'Producto Test',
+ 'descripción' => 'Descripción detallada',
+ 'precio' => 99.99,
+ 'disponibilidad' => 'En stock'
+ ];
+ }
+
+ /**
+ * @Then debería ver:
+ */
+ public function deberiaVer(TableNode $table)
+ {
+ $camposEsperados = array_column($table->getRows(), 0);
+ $camposProducto = array_keys($this->selectedProduct);
+
+ foreach ($camposEsperados as $campo) {
+ $encontrado = false;
+ foreach ($camposProducto as $campoProducto) {
+ if (mb_strtolower($campo) === mb_strtolower($campoProducto)) {
+ $encontrado = true;
+ break;
+ }
+ }
+ Assert::assertTrue($encontrado, "Campo '$campo' no encontrado en el producto");
+ }
+ }
+
+ /**
+ * @When accedo a la tienda
+ */
+ public function accedoALaTienda()
+ {
+ $this->currentPage = 'shop';
+ }
+
+ /**
+ * @When aplico filtros:
+ */
+ public function aplicoFiltros(TableNode $table)
+ {
+ $this->filters = $table->getRowsHash();
+ }
+
+ /**
+ * @Then debería ver solo productos que cumplan los criterios
+ */
+ public function deberiaVerSoloProductosQueCumplanLosCriterios()
+ {
+ Assert::assertNotEmpty($this->filters);
+ }
+
+ /**
+ * @When veo un producto
+ */
+ public function veoUnProducto()
+ {
+ $this->currentPage = 'product';
+ $this->selectedProduct = [
+ 'id' => 1,
+ 'nombre' => 'Producto Test'
+ ];
+ }
+
+ /**
+ * @Then debería poder compartir en redes sociales:
+ */
+ public function deberiaPoderCompartirEnRedesSociales(TableNode $table)
+ {
+ $redesSociales = array_column($table->getRows(), 0);
+ Assert::assertNotEmpty($redesSociales);
+ }
+
+ /**
+ * @Then no debería poder continuar
+ */
+ public function noDeberiaPoderContinuar()
+ {
+ Assert::assertFalse($this->checkoutData['valido'] ?? false);
+ }
+
+ /**
+ * @Then debería ver mis pedidos anteriores
+ */
+ public function deberiaVerMisPedidosAnteriores()
+ {
+ $this->pedidos = [
+ [
+ 'fecha' => '2024-03-20',
+ 'total' => 199.99,
+ 'estado' => 'Completado',
+ 'método de pago' => 'Tarjeta'
+ ]
+ ];
+ Assert::assertNotEmpty($this->pedidos);
+ }
+
+ /**
+ * @Then cada pedido debería mostrar:
+ */
+ public function cadaPedidoDeberiaMostrar(TableNode $table)
+ {
+ $camposRequeridos = array_column($table->getRows(), 0);
+ $camposPedido = array_keys($this->pedidos[0]);
+
+ foreach ($camposRequeridos as $campo) {
+ Assert::assertTrue(
+ in_array(strtolower($campo), array_map('strtolower', $camposPedido)),
+ "Campo '$campo' no encontrado en el pedido"
+ );
+ }
+ }
+
+ /**
+ * @When completo el formulario:
+ */
+ public function completoElFormulario(TableNode $table)
+ {
+ $this->formData = $table->getRowsHash();
+ $this->lastMessage = 'Mensaje enviado correctamente';
+ $_SESSION['message'] = $this->lastMessage;
+ }
+
+ /**
+ * @BeforeScenario
+ */
+ public function initializeSession()
+ {
+ if (session_status() === PHP_SESSION_NONE) {
+ session_start();
+ }
+ $_SESSION['message'] = '';
+ $this->lastMessage = '';
+ }
+}
\ No newline at end of file
diff --git a/tests/BDD/features/user.feature b/tests/BDD/features/user.feature
new file mode 100644
index 00000000..9d9fead5
--- /dev/null
+++ b/tests/BDD/features/user.feature
@@ -0,0 +1,105 @@
+# language: es
+Característica: Funcionalidades de Usuario
+ Como usuario del sistema
+ Quiero gestionar mis compras y perfil
+ Para tener una experiencia de compra satisfactoria
+
+ Antecedentes:
+ Dado que estoy logueado como usuario
+ Y estoy en la página principal
+
+ Escenario: Ver página de inicio
+ Cuando accedo a "home"
+ Entonces debería ver la sección de últimos productos
+ Y debería ver la sección "Sobre nosotros"
+
+ Escenario: Buscar productos
+ Cuando accedo a "search_page"
+ Y busco el término "película"
+ Entonces debería ver productos relacionados con "película"
+
+ Escenario: Búsqueda sin resultados
+ Cuando busco el término "xyzabc123"
+ Entonces debería ver el mensaje "¡No se han encontrado resultados!"
+
+ Escenario: Ver todos los productos
+ Cuando accedo a "shop"
+
+ Escenario: Agregar producto al carrito
+ Dado que estoy en la tienda
+ Cuando selecciono un producto
+ Y establezco cantidad "2"
+ Y presiono "Añadir al carrito"
+ Entonces debería ver el mensaje "Producto agregado al carrito"
+
+ Escenario: Proceso de checkout
+ Dado que tengo productos en el carrito
+ Cuando accedo al checkout
+ Y completo los datos de envío:
+ | nombre | Juan Pérez |
+ | email | juan@email.com |
+ | teléfono | 987654321 |
+ | dirección | Calle 123 |
+ Y selecciono método de pago "Pago en persona"
+ Entonces debería poder finalizar la compra
+
+ Escenario: Ver historial de pedidos
+ Cuando accedo a "orders"
+ Entonces debería ver mis pedidos anteriores
+ Y cada pedido debería mostrar:
+ | Fecha |
+ | Total |
+ | Estado |
+ | Método de pago |
+
+ Escenario: Enviar mensaje de contacto
+ Cuando accedo a "contact"
+ Y completo el formulario:
+ | nombre | Juan Pérez |
+ | email | juan@email.com |
+ | mensaje | Consulta general |
+
+ Escenario: Validar carrito vacío
+ Cuando accedo al checkout
+ Y no tengo productos en el carrito
+
+ Escenario: Actualizar cantidad en carrito
+ Dado que tengo productos en el carrito
+ Cuando actualizo la cantidad de un producto
+ Entonces el total debería actualizarse
+ Y debería ver el nuevo subtotal
+
+ Escenario: Eliminar producto del carrito
+ Dado que tengo productos en el carrito
+ Cuando elimino un producto
+ Entonces debería ver el mensaje "Producto eliminado del carrito"
+ Y el total debería actualizarse
+
+ Escenario: Validar datos de envío
+ Cuando intento hacer checkout
+ Y no completo todos los campos requeridos
+ Entonces debería ver mensajes de validación
+ Y no debería poder continuar
+
+ Escenario: Ver detalles de producto
+ Cuando selecciono un producto específico
+ Entonces debería ver:
+ | Nombre completo |
+ | Descripción |
+ | Precio |
+ | Disponibilidad |
+
+ Escenario: Filtrar productos
+ Cuando accedo a la tienda
+ Y aplico filtros:
+ | Categoría | Series |
+ | Precio | 0-100 |
+ Entonces debería ver solo productos que cumplan los criterios
+
+ Escenario: Compartir producto
+ Cuando veo un producto
+ Y presiono "Compartir"
+ Entonces debería poder compartir en redes sociales:
+ | Facebook |
+ | Twitter |
+ | WhatsApp |
\ No newline at end of file
diff --git a/tests/UI/Pages/AdminLoginTest.php b/tests/UI/Pages/AdminLoginTest.php
new file mode 100644
index 00000000..8f123f57
--- /dev/null
+++ b/tests/UI/Pages/AdminLoginTest.php
@@ -0,0 +1,239 @@
+addArguments([
+ '--start-maximized',
+ '--disable-infobars',
+ '--no-sandbox',
+ '--disable-dev-shm-usage',
+ '--window-size=1920,1080'
+ ]);
+
+ $capabilities = DesiredCapabilities::chrome();
+ $capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
+
+ $testName = $this->getName();
+ $sessionName = match($testName) {
+ 'testAdminLoginSuccessful' => 'Session_AdminLogin_Exitoso',
+ 'testAdminLoginWithInvalidCredentials' => 'Session_AdminLogin_Fallido',
+ 'testAdminLogoutSuccessful' => 'Session_AdminLogout_Exitoso',
+ default => 'Session_' . $testName
+ };
+
+ echo sprintf(
+ "%s\nOS Logo\nBrowser Logo\nv.%s\n",
+ $sessionName,
+ '130.0.6723.91'
+ );
+
+ $videoFileName = sprintf(
+ 'admin_%s_%s.mp4',
+ str_replace('test', '', strtolower($testName)),
+ date('Y-m-d_H-i-s')
+ );
+
+ echo "Nombre del archivo de video: " . $videoFileName . "\n";
+
+ $capabilities->setCapability('selenoid:options', [
+ 'enableVideo' => true,
+ 'videoName' => $videoFileName,
+ 'enableVNC' => true,
+ 'name' => $sessionName,
+ 'sessionTimeout' => '15m',
+ 'timeZone' => 'America/Lima',
+ 'labels' => [
+ 'session' => $sessionName,
+ 'browser' => 'chrome',
+ 'version' => '130.0.6723.91',
+ 'test' => str_replace('test', '', $testName)
+ ]
+ ]);
+
+ $this->driver = RemoteWebDriver::create($host, $capabilities);
+ }
+
+ /**
+ * @test
+ * @testdox 1. Prueba de inicio de sesión exitoso como administrador
+ */
+ public function testAdminLoginVisual()
+ {
+ try {
+ echo "Intentando cargar la página de login del administrador...\n";
+ $this->driver->get($this->baseUrl . '/views/auth/login.php');
+ sleep(2);
+
+ echo "URL actual: " . $this->driver->getCurrentURL() . "\n";
+
+ echo "Ingresando credenciales de administrador...\n";
+ $this->driver->findElement(WebDriverBy::name('email'))
+ ->sendKeys('admin@hotmail.com');
+ sleep(1);
+
+ $this->driver->findElement(WebDriverBy::name('password'))
+ ->sendKeys('123456');
+ sleep(1);
+
+ echo "Haciendo clic en el botón de login...\n";
+ $submitButton = $this->driver->findElement(WebDriverBy::name('submit'));
+ $submitButton->click();
+
+ sleep(3);
+
+ $currentUrl = $this->driver->getCurrentURL();
+ echo "URL después del login: " . $currentUrl . "\n";
+
+ $this->assertStringContainsString(
+ '/views/admin/admin_page.php',
+ $currentUrl,
+ 'La redirección al panel de administrador no fue exitosa'
+ );
+
+ $this->assertNotNull(
+ $this->driver->findElement(WebDriverBy::className('dashboard')),
+ 'No se encontró el dashboard del administrador'
+ );
+
+ $titleElement = $this->driver->findElement(WebDriverBy::className('title'));
+ $this->assertNotNull($titleElement, 'No se encontró el título del panel');
+ $this->assertEquals('PANEL DE CONTROL', $titleElement->getText());
+ } catch (\Exception $e) {
+ echo "Error durante la prueba de administrador: " . $e->getMessage() . "\n";
+ throw $e;
+ }
+ }
+
+ /**
+ * @test
+ * @testdox 2. Prueba de inicio de sesión fallido con credenciales incorrectas
+ */
+ public function testAdminLoginFailure()
+ {
+ try {
+ echo "Probando login de administrador con credenciales incorrectas...\n";
+ $this->driver->get($this->baseUrl . '/views/auth/login.php');
+ sleep(2);
+
+ echo "URL actual: " . $this->driver->getCurrentURL() . "\n";
+
+ echo "Ingresando credenciales incorrectas...\n";
+ $this->driver->findElement(WebDriverBy::name('email'))
+ ->sendKeys('admin_incorrecto@hotmail.com');
+ sleep(1);
+
+ $this->driver->findElement(WebDriverBy::name('password'))
+ ->sendKeys('contraseña_incorrecta');
+ sleep(1);
+
+ echo "Haciendo clic en el botón de login...\n";
+ $submitButton = $this->driver->findElement(WebDriverBy::name('submit'));
+ $submitButton->click();
+
+ sleep(2);
+
+ $currentUrl = $this->driver->getCurrentURL();
+ $this->assertStringContainsString(
+ '/views/auth/login.php',
+ $currentUrl,
+ 'La página no permaneció en el login después de credenciales incorrectas'
+ );
+
+ $errorMessage = $this->driver->findElement(WebDriverBy::className('message'));
+ $this->assertNotNull($errorMessage, 'No se mostró mensaje de error');
+ $this->assertStringContainsString(
+ 'Correo o contraseña incorrectos',
+ $errorMessage->getText(),
+ 'El mensaje de error no es el esperado'
+ );
+
+ echo "Prueba de login fallido completada exitosamente\n";
+ } catch (\Exception $e) {
+ echo "Error durante la prueba de login fallido: " . $e->getMessage() . "\n";
+ $screenshot = $this->driver->takeScreenshot();
+ $filename = 'error_screenshot_' . date('Y-m-d_H-i-s') . '.png';
+ file_put_contents($filename, $screenshot);
+ echo "Screenshot guardado como: " . $filename . "\n";
+ throw $e;
+ }
+ }
+
+ /**
+ * @test
+ * @testdox 3. Prueba de cierre de sesión exitoso del administrador
+ */
+ public function testAdminLogoutVisual()
+ {
+ try {
+ echo "Preparando prueba de logout - Iniciando sesión primero...\n";
+ $this->driver->get($this->baseUrl . '/views/auth/login.php');
+ sleep(2);
+
+ echo "Ingresando credenciales de administrador...\n";
+ $this->driver->findElement(WebDriverBy::name('email'))
+ ->sendKeys('admin@hotmail.com');
+ $this->driver->findElement(WebDriverBy::name('password'))
+ ->sendKeys('123456');
+
+ echo "Haciendo clic en el botón de login...\n";
+ $this->driver->findElement(WebDriverBy::name('submit'))->click();
+ sleep(2);
+
+ $currentUrl = $this->driver->getCurrentURL();
+ $this->assertStringContainsString(
+ '/views/admin/admin_page.php',
+ $currentUrl,
+ 'No se pudo acceder al panel de administrador'
+ );
+
+ echo "Abriendo menú de usuario...\n";
+ $userBtn = $this->driver->findElement(WebDriverBy::id('user-btn'));
+ $userBtn->click();
+ sleep(1);
+
+ echo "Haciendo clic en cerrar sesión...\n";
+ $logoutButton = $this->driver->findElement(WebDriverBy::cssSelector('.delete-btn'));
+ $logoutButton->click();
+ sleep(2);
+
+ $currentUrl = $this->driver->getCurrentURL();
+ $this->assertStringContainsString(
+ '/views/auth/login.php',
+ $currentUrl,
+ 'La redirección al login después del logout no fue exitosa'
+ );
+
+ echo "Prueba de logout completada exitosamente\n";
+ } catch (\Exception $e) {
+ echo "Error durante la prueba de logout: " . $e->getMessage() . "\n";
+ $screenshot = $this->driver->takeScreenshot();
+ $filename = 'error_screenshot_' . date('Y-m-d_H-i-s') . '.png';
+ file_put_contents($filename, $screenshot);
+ echo "Screenshot guardado como: " . $filename . "\n";
+ throw $e;
+ }
+ }
+
+ protected function tearDown(): void
+ {
+ if ($this->driver) {
+ $this->driver->quit();
+ }
+ }
+}
diff --git a/tests/UI/Pages/AdminOrderTest.php b/tests/UI/Pages/AdminOrderTest.php
new file mode 100644
index 00000000..93f9ef29
--- /dev/null
+++ b/tests/UI/Pages/AdminOrderTest.php
@@ -0,0 +1,114 @@
+addArguments([
+ '--start-maximized',
+ '--disable-infobars',
+ '--no-sandbox',
+ '--disable-dev-shm-usage',
+ '--window-size=1920,1080'
+ ]);
+
+ $capabilities = DesiredCapabilities::chrome();
+ $capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
+
+ $this->driver = RemoteWebDriver::create($host, $capabilities);
+
+ $this->adminLogin();
+ }
+
+ private function adminLogin()
+ {
+ $this->driver->get($this->baseUrl . '/views/auth/login.php');
+ sleep(1);
+
+ $emailInput = $this->driver->findElement(WebDriverBy::name('email'));
+ $emailInput->clear();
+ $emailInput->sendKeys('admin@hotmail.com');
+ sleep(0.5);
+
+ $passwordInput = $this->driver->findElement(WebDriverBy::name('password'));
+ $passwordInput->clear();
+ $passwordInput->sendKeys('123456');
+ sleep(0.5);
+
+ $this->driver->findElement(WebDriverBy::name('submit'))->click();
+ sleep(1);
+ }
+
+ public function testUpdateOrderStatus()
+ {
+ try {
+ $this->updateOrderStatus('pendiente', 'completado');
+ sleep(1);
+
+ $this->updateOrderStatus('completado', 'pendiente');
+
+ } catch (\Exception $e) {
+ echo "Error en prueba de órdenes: " . $e->getMessage() . "\n";
+ echo "URL actual: " . $this->driver->getCurrentURL() . "\n";
+ echo "HTML de la página: " . $this->driver->getPageSource() . "\n";
+ throw $e;
+ }
+ }
+
+ private function updateOrderStatus($currentStatus, $newStatus)
+ {
+ $this->driver->get($this->baseUrl . '/views/admin/admin_orders.php');
+ sleep(1);
+
+ $wait = new WebDriverWait($this->driver, 10);
+
+ $orderForm = $wait->until(
+ WebDriverExpectedCondition::presenceOfElementLocated(
+ WebDriverBy::xpath("//select[@name='update_payment']/option[@selected and contains(text(), '$currentStatus')]/../..")
+ )
+ );
+ sleep(0.5);
+
+ $select = $orderForm->findElement(WebDriverBy::name('update_payment'));
+ $select->findElement(WebDriverBy::xpath("//option[@value='$newStatus']"))->click();
+ sleep(0.5);
+
+ $orderForm->findElement(WebDriverBy::cssSelector('input[name="update_order"]'))->click();
+ sleep(1);
+
+ $this->driver->get($this->baseUrl . '/views/admin/admin_page.php');
+ sleep(1);
+
+ $this->assertStringContainsString(
+ 'admin_page.php',
+ $this->driver->getCurrentURL(),
+ 'No se redirigió correctamente a la página de administración'
+ );
+
+ sleep(0.5);
+ }
+
+ protected function tearDown(): void
+ {
+ if ($this->driver) {
+ sleep(1);
+ $this->driver->quit();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/UI/Pages/AdminProductTest.php b/tests/UI/Pages/AdminProductTest.php
new file mode 100644
index 00000000..b8e316b6
--- /dev/null
+++ b/tests/UI/Pages/AdminProductTest.php
@@ -0,0 +1,145 @@
+addArguments([
+ '--start-maximized',
+ '--disable-infobars',
+ '--no-sandbox',
+ '--disable-dev-shm-usage',
+ '--window-size=1920,1080'
+ ]);
+
+ $capabilities = DesiredCapabilities::chrome();
+ $capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
+
+ $this->driver = RemoteWebDriver::create($host, $capabilities);
+
+ $this->adminLogin();
+ }
+
+ private function adminLogin()
+ {
+ $this->driver->get($this->baseUrl . '/views/auth/login.php');
+ $this->driver->findElement(WebDriverBy::name('email'))->sendKeys('admin@hotmail.com');
+ $this->driver->findElement(WebDriverBy::name('password'))->sendKeys('123456');
+ $this->driver->findElement(WebDriverBy::name('submit'))->click();
+ sleep(2);
+ }
+
+ public function testAddAndUpdateProduct()
+ {
+ try {
+ $this->driver->get($this->baseUrl . '/views/admin/admin_products.php');
+
+ $wait = new WebDriverWait($this->driver, 10);
+
+ $form = $wait->until(
+ WebDriverExpectedCondition::presenceOfElementLocated(
+ WebDriverBy::cssSelector('.add-products form')
+ )
+ );
+
+ $productName = 'Producto UI Test';
+
+ $nameInput = $form->findElement(WebDriverBy::cssSelector('input[name="name"]'));
+ $nameInput->clear();
+ $nameInput->sendKeys($productName);
+
+ $priceInput = $form->findElement(WebDriverBy::cssSelector('input[name="price"]'));
+ $priceInput->clear();
+ $priceInput->sendKeys('15');
+
+ $form->findElement(WebDriverBy::cssSelector('input[name="add_product"]'))->click();
+
+ $wait = new WebDriverWait($this->driver, 10);
+ $productElement = $wait->until(
+ WebDriverExpectedCondition::presenceOfElementLocated(
+ WebDriverBy::xpath("//div[contains(@class, 'name') and contains(text(), '$productName')]")
+ )
+ );
+
+ $this->assertStringContainsString(
+ $productName,
+ $productElement->getText(),
+ 'El producto no se encontró en la lista después de agregarlo'
+ );
+
+ $newProductName = 'Producto UI Test Actualizado';
+
+ $updateButton = $this->driver->findElement(
+ WebDriverBy::xpath("//div[contains(@class, 'name') and contains(text(), '$productName')]/..//a[contains(@class, 'option-btn')]")
+ );
+ $updateButton->click();
+
+ $wait = new WebDriverWait($this->driver, 10);
+ $updateForm = $wait->until(
+ WebDriverExpectedCondition::presenceOfElementLocated(
+ WebDriverBy::cssSelector('.edit-product-form form')
+ )
+ );
+
+ $updateNameInput = $updateForm->findElement(WebDriverBy::cssSelector('input[name="update_name"]'));
+ $updateNameInput->clear();
+ $updateNameInput->sendKeys($newProductName);
+
+ $updatePriceInput = $updateForm->findElement(WebDriverBy::cssSelector('input[name="update_price"]'));
+ $updatePriceInput->clear();
+ $updatePriceInput->sendKeys('25');
+
+ $updateForm->findElement(WebDriverBy::cssSelector('input[name="update_product"]'))->click();
+
+ $updatedProductElement = $wait->until(
+ WebDriverExpectedCondition::presenceOfElementLocated(
+ WebDriverBy::xpath("//div[contains(@class, 'name') and contains(text(), '$newProductName')]")
+ )
+ );
+
+ $this->assertStringContainsString(
+ $newProductName,
+ $updatedProductElement->getText(),
+ 'El producto no se actualizó correctamente'
+ );
+
+ $updatedPriceElement = $this->driver->findElement(
+ WebDriverBy::xpath("//div[contains(@class, 'name') and contains(text(), '$newProductName')]/..//div[contains(@class, 'price')]")
+ );
+ $this->assertStringContainsString(
+ '25',
+ $updatedPriceElement->getText(),
+ 'El precio del producto no se actualizó correctamente'
+ );
+
+ } catch (\Exception $e) {
+ echo "Error en prueba de productos: " . $e->getMessage() . "\n";
+ echo "URL actual: " . $this->driver->getCurrentURL() . "\n";
+ echo "HTML de la página: " . $this->driver->getPageSource() . "\n";
+ throw $e;
+ }
+ }
+
+ protected function tearDown(): void
+ {
+ if ($this->driver) {
+ $this->driver->quit();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/UI/Pages/AdminUsersMessagesTest.php b/tests/UI/Pages/AdminUsersMessagesTest.php
new file mode 100644
index 00000000..08a63fc4
--- /dev/null
+++ b/tests/UI/Pages/AdminUsersMessagesTest.php
@@ -0,0 +1,176 @@
+addArguments([
+ '--start-maximized',
+ '--disable-infobars',
+ '--no-sandbox',
+ '--disable-dev-shm-usage',
+ '--window-size=1920,1080'
+ ]);
+
+ $capabilities = DesiredCapabilities::chrome();
+ $capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
+
+ $this->driver = RemoteWebDriver::create($host, $capabilities);
+
+ $this->adminLogin();
+ }
+
+ private function adminLogin()
+ {
+ $this->driver->get($this->baseUrl . '/views/auth/login.php');
+ sleep(1);
+
+ $emailInput = $this->driver->findElement(WebDriverBy::name('email'));
+ $emailInput->clear();
+ $emailInput->sendKeys('admin@hotmail.com');
+ sleep(0.5);
+
+ $passwordInput = $this->driver->findElement(WebDriverBy::name('password'));
+ $passwordInput->clear();
+ $passwordInput->sendKeys('123456');
+ sleep(0.5);
+
+ $this->driver->findElement(WebDriverBy::name('submit'))->click();
+ sleep(1);
+ }
+
+ public function testViewUsersAndMessagesAndLogout()
+ {
+ try {
+ $this->checkUsersSection();
+ sleep(1);
+
+ $this->checkMessagesSection();
+ sleep(1);
+
+ $this->performLogout();
+
+ } catch (\Exception $e) {
+ echo "Error en prueba: " . $e->getMessage() . "\n";
+ echo "URL actual: " . $this->driver->getCurrentURL() . "\n";
+ echo "HTML de la página: " . $this->driver->getPageSource() . "\n";
+ throw $e;
+ }
+ }
+
+ private function checkUsersSection()
+ {
+ $this->driver->get($this->baseUrl . '/views/admin/admin_users.php');
+ sleep(1);
+
+ $this->assertStringContainsString(
+ 'admin_users.php',
+ $this->driver->getCurrentURL(),
+ 'No se pudo acceder a la página de usuarios'
+ );
+
+ $wait = new WebDriverWait($this->driver, 10);
+ $usersContainer = $wait->until(
+ WebDriverExpectedCondition::presenceOfElementLocated(
+ WebDriverBy::cssSelector('.box-container')
+ )
+ );
+
+ $users = $this->driver->findElements(WebDriverBy::cssSelector('.box'));
+ $emptyMessage = $this->driver->findElements(WebDriverBy::cssSelector('.empty'));
+
+ $this->assertTrue(
+ count($users) > 0 || count($emptyMessage) > 0,
+ 'No se encontraron usuarios ni mensaje de "no hay usuarios"'
+ );
+ }
+
+ private function checkMessagesSection()
+ {
+ $this->driver->get($this->baseUrl . '/views/admin/admin_contacts.php');
+ sleep(1);
+
+ $this->assertStringContainsString(
+ 'admin_contacts.php',
+ $this->driver->getCurrentURL(),
+ 'No se pudo acceder a la página de mensajes'
+ );
+
+ $wait = new WebDriverWait($this->driver, 10);
+ $messagesContainer = $wait->until(
+ WebDriverExpectedCondition::presenceOfElementLocated(
+ WebDriverBy::cssSelector('.box-container')
+ )
+ );
+
+ $messages = $this->driver->findElements(WebDriverBy::cssSelector('.box'));
+ $emptyMessage = $this->driver->findElements(WebDriverBy::cssSelector('.empty'));
+
+ $this->assertTrue(
+ count($messages) > 0 || count($emptyMessage) > 0,
+ 'No se encontraron mensajes ni mensaje de "no hay mensajes"'
+ );
+
+ if (count($messages) > 0) {
+ $deleteButtons = $this->driver->findElements(WebDriverBy::cssSelector('.delete-btn'));
+ $this->assertGreaterThan(0, count($deleteButtons), 'No se encontraron botones de eliminar');
+ }
+ }
+
+ private function performLogout()
+ {
+ $userBtn = $this->driver->findElement(WebDriverBy::id('user-btn'));
+ $userBtn->click();
+ sleep(1);
+
+ $wait = new WebDriverWait($this->driver, 10);
+ $accountBox = $wait->until(
+ WebDriverExpectedCondition::visibilityOfElementLocated(
+ WebDriverBy::cssSelector('.account-box')
+ )
+ );
+
+ $logoutBtn = $accountBox->findElement(WebDriverBy::cssSelector('.delete-btn'));
+ $logoutBtn->click();
+ sleep(1);
+
+ $this->assertStringContainsString(
+ 'login.php',
+ $this->driver->getCurrentURL(),
+ 'No se redirigió correctamente a la página de login después de cerrar sesión'
+ );
+
+ $this->driver->get($this->baseUrl . '/views/admin/admin_page.php');
+ sleep(1);
+
+ $this->assertStringContainsString(
+ 'login.php',
+ $this->driver->getCurrentURL(),
+ 'No se está protegiendo correctamente el acceso a páginas de administrador después del logout'
+ );
+ }
+
+ protected function tearDown(): void
+ {
+ if ($this->driver) {
+ sleep(1);
+ $this->driver->quit();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/UI/Pages/ContactPageTest.php b/tests/UI/Pages/ContactPageTest.php
new file mode 100644
index 00000000..7942761b
--- /dev/null
+++ b/tests/UI/Pages/ContactPageTest.php
@@ -0,0 +1,74 @@
+addArguments(['--start-maximized', '--disable-infobars', '--no-sandbox']);
+
+ $capabilities = DesiredCapabilities::chrome();
+ $capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
+
+ $this->driver = RemoteWebDriver::create($host, $capabilities);
+
+ $this->login();
+ }
+
+ private function login()
+ {
+ $this->driver->get($this->baseUrl . '/views/auth/login.php');
+ $this->driver->findElement(WebDriverBy::name('email'))->sendKeys('test@hotmail.com');
+ $this->driver->findElement(WebDriverBy::name('password'))->sendKeys('123456');
+ $this->driver->findElement(WebDriverBy::name('submit'))->click();
+ sleep(2);
+ }
+
+ public function testSendContactForm()
+ {
+ $this->driver->get($this->baseUrl . '/views/usuario/contact.php');
+
+ $this->driver->findElement(WebDriverBy::name('name'))->sendKeys('Usuario Test');
+ $this->driver->findElement(WebDriverBy::name('email'))->sendKeys('test@example.com');
+ $this->driver->findElement(WebDriverBy::name('number'))->sendKeys('123456789');
+ $this->driver->findElement(WebDriverBy::name('message'))->sendKeys('Este es un mensaje de prueba');
+
+ $this->driver->findElement(WebDriverBy::name('send'))->click();
+
+ $wait = new WebDriverWait($this->driver, 10);
+ $messageElement = $wait->until(
+ WebDriverExpectedCondition::presenceOfElementLocated(WebDriverBy::cssSelector('.message'))
+ );
+
+ $actualMessage = $messageElement->getText();
+ $this->assertTrue(
+ in_array($actualMessage, [
+ '¡Mensaje ya enviado!',
+ '¡Mensaje enviado exitosamente!'
+ ]),
+ "El mensaje '$actualMessage' no coincide con ninguno de los mensajes esperados"
+ );
+ }
+
+ protected function tearDown(): void
+ {
+ if ($this->driver) {
+ $this->driver->quit();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/UI/Pages/LoginPageTest.php b/tests/UI/Pages/LoginPageTest.php
new file mode 100644
index 00000000..4052e713
--- /dev/null
+++ b/tests/UI/Pages/LoginPageTest.php
@@ -0,0 +1,159 @@
+addArguments([
+ '--start-maximized',
+ '--disable-infobars',
+ '--no-sandbox',
+ '--disable-dev-shm-usage',
+ '--window-size=1920,1080'
+ ]);
+
+ $capabilities = DesiredCapabilities::chrome();
+ $capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
+
+ $videoFileName = 'user_login_test_' . date('Y-m-d_H-i-s') . '.mp4';
+ echo "Nombre del archivo de video: " . $videoFileName . "\n";
+
+ $capabilities->setCapability('selenoid:options', [
+ 'enableVideo' => true,
+ 'videoName' => $videoFileName,
+ 'enableVNC' => true,
+ 'name' => 'User Login Test Recording'
+ ]);
+
+ $this->driver = RemoteWebDriver::create($host, $capabilities);
+ }
+
+ public function testUserLoginVisual()
+ {
+ try {
+ echo "Intentando cargar la página de login del usuario...\n";
+ $this->driver->get($this->baseUrl . '/views/auth/login.php');
+ sleep(2);
+
+ echo "URL actual: " . $this->driver->getCurrentURL() . "\n";
+
+ $this->assertNotNull(
+ $this->driver->findElement(WebDriverBy::className('form-container')),
+ 'No se encontró el contenedor del formulario'
+ );
+
+ echo "Ingresando credenciales de usuario...\n";
+ $this->driver->findElement(WebDriverBy::name('email'))
+ ->sendKeys('test@hotmail.com');
+ sleep(1);
+
+ $this->driver->findElement(WebDriverBy::name('password'))
+ ->sendKeys('123456');
+ sleep(1);
+
+ echo "Haciendo clic en el botón de login...\n";
+ $submitButton = $this->driver->findElement(WebDriverBy::name('submit'));
+ $submitButton->click();
+
+ sleep(3);
+
+ $currentUrl = $this->driver->getCurrentURL();
+ echo "URL después del login: " . $currentUrl . "\n";
+
+ $this->assertStringContainsString(
+ '/views/usuario/home.php',
+ $currentUrl,
+ 'La redirección a la página de usuario no fue exitosa'
+ );
+
+ sleep(2);
+
+ $this->assertNotNull(
+ $this->driver->findElement(WebDriverBy::className('home')),
+ 'No se encontró la sección home'
+ );
+
+ $this->assertNotNull(
+ $this->driver->findElement(WebDriverBy::className('products')),
+ 'No se encontró la sección de productos'
+ );
+
+ $this->assertNotNull(
+ $this->driver->findElement(WebDriverBy::className('about')),
+ 'No se encontró la sección about'
+ );
+
+ $this->assertNotNull(
+ $this->driver->findElement(WebDriverBy::className('home-contact')),
+ 'No se encontró la sección de contacto'
+ );
+
+ $titleText = $this->driver->findElement(WebDriverBy::className('title'))->getText();
+ $this->assertEquals('ÚLTIMOS PRODUCTOS', $titleText);
+
+ $this->assertNotNull(
+ $this->driver->findElement(WebDriverBy::tagName('header')),
+ 'No se encontró el header'
+ );
+
+ } catch (\Exception $e) {
+ echo "Error durante la prueba de usuario: " . $e->getMessage() . "\n";
+ throw $e;
+ }
+ }
+
+ public function testLoginFailure()
+ {
+ try {
+ echo "Probando login con credenciales incorrectas...\n";
+ $this->driver->get($this->baseUrl . '/views/auth/login.php');
+ sleep(2);
+
+ $this->driver->findElement(WebDriverBy::name('email'))
+ ->sendKeys('usuario_invalido@hotmail.com');
+ sleep(1);
+
+ $this->driver->findElement(WebDriverBy::name('password'))
+ ->sendKeys('contraseña_incorrecta');
+ sleep(1);
+
+ $submitButton = $this->driver->findElement(WebDriverBy::name('submit'));
+ $submitButton->click();
+
+ sleep(2);
+
+ $errorMessage = $this->driver->findElement(WebDriverBy::className('message'));
+ $this->assertNotNull($errorMessage, 'No se mostró mensaje de error');
+ $this->assertStringContainsString(
+ 'Correo o contraseña incorrectos',
+ $errorMessage->getText(),
+ 'El mensaje de error no es el esperado'
+ );
+
+ } catch (\Exception $e) {
+ echo "Error durante la prueba de login fallido: " . $e->getMessage() . "\n";
+ throw $e;
+ }
+ }
+
+ protected function tearDown(): void
+ {
+ if ($this->driver) {
+ $this->driver->quit();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/UI/Pages/RegisterPageTest.php b/tests/UI/Pages/RegisterPageTest.php
new file mode 100644
index 00000000..a4b52e03
--- /dev/null
+++ b/tests/UI/Pages/RegisterPageTest.php
@@ -0,0 +1,146 @@
+addArguments([
+ '--start-maximized',
+ '--disable-infobars',
+ '--no-sandbox',
+ '--disable-dev-shm-usage',
+ '--window-size=1920,1080'
+ ]);
+
+ $capabilities = DesiredCapabilities::chrome();
+ $capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
+
+ $videoFileName = 'user_register_test_' . date('Y-m-d_H-i-s') . '.mp4';
+ echo "Nombre del archivo de video: " . $videoFileName . "\n";
+
+ $capabilities->setCapability('selenoid:options', [
+ 'enableVideo' => true,
+ 'videoName' => $videoFileName,
+ 'enableVNC' => true,
+ 'name' => 'User Register Test Recording'
+ ]);
+
+ $this->driver = RemoteWebDriver::create($host, $capabilities);
+ }
+
+ public function testUserRegisterSuccess()
+ {
+ try {
+ echo "Intentando cargar la página de registro...\n";
+ $this->driver->get($this->baseUrl . '/views/auth/register.php');
+ sleep(2);
+
+ $this->assertNotNull(
+ $this->driver->findElement(WebDriverBy::className('form-container')),
+ 'No se encontró el contenedor del formulario'
+ );
+
+ $uniqueEmail = 'test_' . time() . '@hotmail.com';
+
+ echo "Ingresando datos de registro...\n";
+ $this->driver->findElement(WebDriverBy::name('name'))
+ ->sendKeys('Usuario Test');
+ sleep(1);
+
+ $this->driver->findElement(WebDriverBy::name('email'))
+ ->sendKeys($uniqueEmail);
+ sleep(1);
+
+ $this->driver->findElement(WebDriverBy::name('password'))
+ ->sendKeys('123456');
+ sleep(1);
+
+ $this->driver->findElement(WebDriverBy::name('cpassword'))
+ ->sendKeys('123456');
+ sleep(1);
+
+ echo "Haciendo clic en el botón de registro...\n";
+ $submitButton = $this->driver->findElement(WebDriverBy::name('submit'));
+ $submitButton->click();
+
+ sleep(3);
+
+ $currentUrl = $this->driver->getCurrentURL();
+ echo "URL después del registro: " . $currentUrl . "\n";
+
+ $this->assertStringContainsString(
+ '/views/auth/login.php',
+ $currentUrl,
+ 'La redirección a la página de login no fue exitosa'
+ );
+
+ } catch (\Exception $e) {
+ echo "Error durante la prueba de registro: " . $e->getMessage() . "\n";
+ throw $e;
+ }
+ }
+
+ public function testRegisterFailure()
+ {
+ try {
+ echo "Probando registro con contraseñas que no coinciden...\n";
+ $this->driver->get($this->baseUrl . '/views/auth/register.php');
+ sleep(2);
+
+ $this->driver->findElement(WebDriverBy::name('name'))
+ ->sendKeys('Usuario Test');
+ sleep(1);
+
+ $this->driver->findElement(WebDriverBy::name('email'))
+ ->sendKeys('test@hotmail.com');
+ sleep(1);
+
+ $this->driver->findElement(WebDriverBy::name('password'))
+ ->sendKeys('123456');
+ sleep(1);
+
+ $this->driver->findElement(WebDriverBy::name('cpassword'))
+ ->sendKeys('654321');
+ sleep(1);
+
+ $submitButton = $this->driver->findElement(WebDriverBy::name('submit'));
+ $submitButton->click();
+
+ sleep(2);
+
+ // Verificar mensaje de error
+ $errorMessage = $this->driver->findElement(WebDriverBy::className('message'));
+ $this->assertNotNull($errorMessage, 'No se mostró mensaje de error');
+ $this->assertStringContainsString(
+ 'Las contraseñas no coinciden!',
+ $errorMessage->getText(),
+ 'El mensaje de error no es el esperado'
+ );
+
+ } catch (\Exception $e) {
+ echo "Error durante la prueba de registro fallido: " . $e->getMessage() . "\n";
+ throw $e;
+ }
+ }
+
+ protected function tearDown(): void
+ {
+ if ($this->driver) {
+ $this->driver->quit();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/UI/Pages/SearchPageTest.php b/tests/UI/Pages/SearchPageTest.php
new file mode 100644
index 00000000..becff805
--- /dev/null
+++ b/tests/UI/Pages/SearchPageTest.php
@@ -0,0 +1,124 @@
+addArguments(['--start-maximized', '--disable-infobars', '--no-sandbox']);
+
+ $capabilities = DesiredCapabilities::chrome();
+ $capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
+
+ $this->driver = RemoteWebDriver::create($host, $capabilities);
+
+ // Login antes de las pruebas
+ $this->login();
+ }
+
+ private function login()
+ {
+ $this->driver->get($this->baseUrl . '/views/auth/login.php');
+ $this->driver->findElement(WebDriverBy::name('email'))->sendKeys('test@hotmail.com');
+ $this->driver->findElement(WebDriverBy::name('password'))->sendKeys('123456');
+ $this->driver->findElement(WebDriverBy::name('submit'))->click();
+ sleep(2);
+ }
+
+ public function testAddToCartFromSearchWithResults()
+ {
+ try {
+ $this->driver->get($this->baseUrl . '/views/usuario/search_page.php');
+ sleep(2);
+
+ $searchInput = $this->driver->findElement(WebDriverBy::name('search'));
+ $searchInput->sendKeys('test');
+
+ $submitButton = $this->driver->findElement(
+ WebDriverBy::cssSelector('.search-form input[type="submit"]')
+ );
+ $submitButton->click();
+ sleep(2);
+
+ $products = $this->driver->findElements(WebDriverBy::className('box'));
+ $this->assertGreaterThan(0, count($products), 'No se encontraron productos');
+
+ $addToCartButton = $this->driver->findElement(
+ WebDriverBy::cssSelector('.box input[type="submit"][value="Agregar al carrito"]')
+ );
+
+ sleep(2);
+ $addToCartButton->click();
+
+ $cartCount = $this->driver->findElement(
+ WebDriverBy::cssSelector('a[href="cart.php"] span')
+ );
+ $this->assertNotEquals('(0)', $cartCount->getText(), 'El contador del carrito no se actualizó');
+ } catch (\Exception $e) {
+ echo "Error en prueba de agregar al carrito con resultados: " . $e->getMessage() . "\n";
+ throw $e;
+ }
+ }
+
+ public function testSearchWithNoResults()
+ {
+ try {
+ $this->driver->get($this->baseUrl . '/views/usuario/search_page.php');
+ sleep(2);
+
+ $searchInput = $this->driver->findElement(WebDriverBy::name('search'));
+ $searchInput->sendKeys('xxxxxxxxxxx123456789');
+
+ $submitButton = $this->driver->findElement(
+ WebDriverBy::cssSelector('.search-form input[type="submit"]')
+ );
+ $submitButton->click();
+
+ sleep(4);
+
+ $currentUrl = $this->driver->getCurrentUrl();
+ echo "\nURL actual: " . $currentUrl . "\n";
+
+ $products = $this->driver->findElements(
+ WebDriverBy::cssSelector('.search-results .box')
+ );
+ echo "\nNúmero de productos encontrados: " . count($products) . "\n";
+
+ foreach ($products as $index => $product) {
+ echo "Producto " . ($index + 1) . ": " . $product->getText() . "\n";
+ }
+
+ $this->assertEquals(0, count($products), 'Se encontraron productos cuando no debería haberlos');
+
+ $emptyMessage = $this->driver->findElement(WebDriverBy::className('empty'));
+ $this->assertEquals(
+ '¡No se han encontrado resultados!',
+ $emptyMessage->getText(),
+ 'Mensaje de búsqueda vacía incorrecto'
+ );
+ } catch (\Exception $e) {
+ echo "Error en prueba de búsqueda sin resultados: " . $e->getMessage() . "\n";
+ throw $e;
+ }
+ }
+
+ protected function tearDown(): void
+ {
+ if ($this->driver) {
+ $this->driver->quit();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/UI/Pages/ShopPageTest.php b/tests/UI/Pages/ShopPageTest.php
new file mode 100644
index 00000000..63f2bdac
--- /dev/null
+++ b/tests/UI/Pages/ShopPageTest.php
@@ -0,0 +1,267 @@
+addArguments(['--start-maximized', '--disable-infobars', '--no-sandbox']);
+
+ $capabilities = DesiredCapabilities::chrome();
+ $capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
+
+ $this->driver = RemoteWebDriver::create($host, $capabilities);
+
+ $this->login();
+ }
+
+ private function login()
+ {
+ $this->driver->get($this->baseUrl . '/views/auth/login.php');
+ $this->driver->findElement(WebDriverBy::name('email'))->sendKeys('test@hotmail.com');
+ $this->driver->findElement(WebDriverBy::name('password'))->sendKeys('123456');
+ $this->driver->findElement(WebDriverBy::name('submit'))->click();
+ sleep(2);
+ }
+
+ public function testShopPageLoadsWithProducts()
+ {
+ try {
+ $this->driver->get($this->baseUrl . '/views/usuario/shop.php');
+ sleep(2);
+
+ $title = $this->driver->findElement(WebDriverBy::cssSelector('.heading h3'));
+ $this->assertEquals('NUESTRA TIENDA', $title->getText());
+
+ $products = $this->driver->findElements(WebDriverBy::cssSelector('.products .box'));
+ $this->assertGreaterThan(0, count($products), 'No se encontraron productos en la tienda');
+
+ $firstProduct = $products[0];
+ $this->assertNotNull($firstProduct->findElement(WebDriverBy::cssSelector('.image')), 'Imagen del producto no encontrada');
+ $this->assertNotNull($firstProduct->findElement(WebDriverBy::cssSelector('.name')), 'Nombre del producto no encontrado');
+ $this->assertNotNull($firstProduct->findElement(WebDriverBy::cssSelector('.price')), 'Precio del producto no encontrado');
+ $this->assertNotNull($firstProduct->findElement(WebDriverBy::cssSelector('.qty')), 'Campo de cantidad no encontrado');
+ $this->assertNotNull($firstProduct->findElement(WebDriverBy::cssSelector('.btn')), 'Botón de añadir al carrito no encontrado');
+ } catch (\Exception $e) {
+ echo "Error en prueba de carga de página de tienda: " . $e->getMessage() . "\n";
+ throw $e;
+ }
+ }
+
+ public function testAddNewProductToCart()
+ {
+ try {
+ $this->driver->get($this->baseUrl . '/views/usuario/shop.php');
+ sleep(2);
+
+ $cartCountSelector = '.header .icons a[href="cart.php"] span';
+ $initialCartCount = (int)$this->driver->findElement(WebDriverBy::cssSelector($cartCountSelector))->getText();
+
+ $addToCartButton = $this->driver->findElement(WebDriverBy::name('add_to_cart'));
+ $addToCartButton->click();
+
+ sleep(2);
+
+ $messages = $this->driver->findElements(WebDriverBy::cssSelector('.message'));
+ foreach ($messages as $message) {
+ if (strpos($message->getText(), 'El producto ya está en el carrito') !== false) {
+ $this->assertTrue(true);
+ return;
+ }
+ }
+
+ $this->assertTrue(true);
+
+ } catch (\Exception $e) {
+ $this->fail("Error en prueba de añadir producto al carrito: " . $e->getMessage());
+ }
+ }
+
+ public function testAddDuplicateProductToCart()
+ {
+ try {
+ $this->driver->get($this->baseUrl . '/views/usuario/shop.php');
+ sleep(2);
+
+ $cartCountSelector = '.header .icons a[href="cart.php"] span';
+ $initialCartCount = (int)$this->driver->findElement(WebDriverBy::cssSelector($cartCountSelector))->getText();
+
+ $addToCartButton = $this->driver->findElement(WebDriverBy::name('add_to_cart'));
+ $addToCartButton->click();
+
+ sleep(2);
+
+ $messages = $this->driver->findElements(WebDriverBy::cssSelector('.message'));
+ $errorFound = false;
+
+ foreach ($messages as $message) {
+ if (strpos($message->getText(), 'El producto ya está en el carrito') !== false) {
+ $errorFound = true;
+ break;
+ }
+ }
+
+ $this->assertTrue($errorFound, "No se mostró el mensaje de error esperado");
+
+ $finalCartCount = (int)$this->driver->findElement(WebDriverBy::cssSelector($cartCountSelector))->getText();
+ $this->assertEquals($initialCartCount, $finalCartCount,
+ "El contador no debería cambiar si el producto ya está en el carrito");
+
+ } catch (\Exception $e) {
+ $this->fail("Error en prueba de añadir producto duplicado al carrito: " . $e->getMessage());
+ }
+ }
+
+ public function testUpdateProductQuantityInCart()
+ {
+ try {
+ $this->driver->get($this->baseUrl . '/views/usuario/shop.php');
+ sleep(2);
+
+ $addToCartButton = $this->driver->findElement(WebDriverBy::name('add_to_cart'));
+ $addToCartButton->click();
+ sleep(2);
+
+ $cartIcon = $this->driver->findElement(WebDriverBy::cssSelector('.header .icons a[href="cart.php"]'));
+ $cartIcon->click();
+ sleep(2);
+
+ $quantityInput = $this->driver->findElement(WebDriverBy::name('cart_quantity'));
+ $initialQuantity = $quantityInput->getAttribute('value');
+
+ $quantityInput->clear();
+ $quantityInput->sendKeys('3');
+
+ $updateButton = $this->driver->findElement(WebDriverBy::cssSelector('input[name="update_cart"]'));
+ $updateButton->click();
+ sleep(2);
+
+ $updatedQuantityInput = $this->driver->findElement(WebDriverBy::name('cart_quantity'));
+ $newQuantity = $updatedQuantityInput->getAttribute('value');
+
+ $this->assertEquals('3', $newQuantity, 'La cantidad no se actualizó correctamente');
+
+ } catch (\Exception $e) {
+ $this->fail("Error en prueba de actualización de cantidad en el carrito: " . $e->getMessage());
+ }
+ }
+
+ public function testProceedToCheckoutAndPlaceOrder()
+ {
+ try {
+ $this->driver->get($this->baseUrl . '/views/usuario/shop.php');
+
+ $wait = new WebDriverWait($this->driver, 10);
+
+ $addToCartButton = $wait->until(
+ WebDriverExpectedCondition::elementToBeClickable(WebDriverBy::name('add_to_cart'))
+ );
+ $addToCartButton->click();
+ sleep(2);
+
+ $this->driver->get($this->baseUrl . '/views/usuario/cart.php');
+ sleep(2);
+
+ $checkoutButton = $wait->until(
+ WebDriverExpectedCondition::elementToBeClickable(WebDriverBy::cssSelector('.cart-total .btn'))
+ );
+ $checkoutButton->click();
+ sleep(2);
+
+ $this->assertStringContainsString('checkout.php', $this->driver->getCurrentUrl());
+
+ $wait->until(WebDriverExpectedCondition::presenceOfElementLocated(WebDriverBy::name('name')))
+ ->sendKeys('Usuario Prueba');
+ $this->driver->findElement(WebDriverBy::name('number'))->sendKeys('987654321');
+ $this->driver->findElement(WebDriverBy::name('email'))->sendKeys('prueba@test.com');
+
+ $select = $this->driver->findElement(WebDriverBy::name('method'));
+ $select->findElement(WebDriverBy::cssSelector("option[value='Tarjeta de crédito']"))->click();
+
+ $this->driver->findElement(WebDriverBy::name('flat'))->sendKeys('123');
+ $this->driver->findElement(WebDriverBy::name('street'))->sendKeys('Calle Principal');
+ $this->driver->findElement(WebDriverBy::name('city'))->sendKeys('Lima');
+ $this->driver->findElement(WebDriverBy::name('state'))->sendKeys('Miraflores');
+ $this->driver->findElement(WebDriverBy::name('country'))->sendKeys('Perú');
+ $this->driver->findElement(WebDriverBy::name('pin_code'))->sendKeys('15046');
+
+ $orderButton = $wait->until(
+ WebDriverExpectedCondition::elementToBeClickable(WebDriverBy::name('order_btn'))
+ );
+ $orderButton->click();
+ sleep(2);
+
+ $message = $wait->until(
+ WebDriverExpectedCondition::presenceOfElementLocated(WebDriverBy::cssSelector('.message'))
+ );
+
+ $messageText = $message->getText();
+ $validMessages = [
+ '¡Pedido realizado con éxito!',
+ '¡Pedido ya realizado!'
+ ];
+
+ $this->assertTrue(
+ in_array($messageText, $validMessages),
+ "Mensaje inesperado: '$messageText'. Se esperaba uno de estos mensajes: " . implode(' o ', $validMessages)
+ );
+
+ } catch (\Exception $e) {
+ $this->fail("Error en prueba de checkout: " . $e->getMessage());
+ }
+ }
+
+ public function testViewOrders()
+ {
+ try {
+ $this->driver->get($this->baseUrl . '/views/usuario/orders.php');
+ sleep(2);
+
+ $currentUrl = $this->driver->getCurrentUrl();
+ $this->assertStringContainsString('orders.php', $currentUrl);
+
+ $ordersSection = $this->driver->findElement(WebDriverBy::cssSelector('.placed-orders'));
+ $this->assertNotNull($ordersSection, 'La sección de órdenes no fue encontrada');
+
+ $boxContainer = $this->driver->findElement(WebDriverBy::cssSelector('.box-container'));
+
+ try {
+ $orderBoxes = $this->driver->findElements(WebDriverBy::cssSelector('.box-container .box'));
+ if (count($orderBoxes) > 0) {
+ $firstOrder = $orderBoxes[0];
+ $this->assertNotNull($firstOrder->findElement(WebDriverBy::xpath(".//p[contains(text(), 'Nombre')]")), 'Nombre no encontrado');
+ $this->assertNotNull($firstOrder->findElement(WebDriverBy::xpath(".//p[contains(text(), 'Número')]")), 'Número no encontrado');
+ $this->assertNotNull($firstOrder->findElement(WebDriverBy::xpath(".//p[contains(text(), 'Tus pedidos')]")), 'Total de productos no encontrado');
+ $this->assertNotNull($firstOrder->findElement(WebDriverBy::xpath(".//p[contains(text(), 'Precio total')]")), 'Precio total no encontrado');
+ $this->assertNotNull($firstOrder->findElement(WebDriverBy::xpath(".//p[contains(text(), 'Fecha')]")), 'Fecha no encontrada');
+ }
+ } catch (\Exception $e) {
+ $this->fail("Error en prueba de visualización de órdenes: " . $e->getMessage());
+ }
+ } catch (\Exception $e) {
+ $this->fail("Error en prueba de visualización de órdenes: " . $e->getMessage());
+ }
+ }
+
+ protected function tearDown(): void
+ {
+ if ($this->driver) {
+ $this->driver->quit();
+ }
+ }
+}
diff --git a/tests/UI/bootstrap.php b/tests/UI/bootstrap.php
new file mode 100644
index 00000000..387d4bb9
--- /dev/null
+++ b/tests/UI/bootstrap.php
@@ -0,0 +1,5 @@
+assertFalse(class_exists($className, false));
+
+ $this->assertTrue(class_exists($className, true));
+ }
+
+ public function test_maneja_clase_inexistente(): void
+ {
+ $className = 'Exceptions\ClaseQueNoExiste';
+
+ $this->assertFalse(class_exists($className, true));
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Config/DatabaseTest.php b/tests/Unit/Config/DatabaseTest.php
new file mode 100644
index 00000000..51cdb50a
--- /dev/null
+++ b/tests/Unit/Config/DatabaseTest.php
@@ -0,0 +1,19 @@
+expectException(DatabaseException::class);
+ $database->connect();
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Controllers/AdminControllerTest.php b/tests/Unit/Controllers/AdminControllerTest.php
new file mode 100644
index 00000000..cef949f3
--- /dev/null
+++ b/tests/Unit/Controllers/AdminControllerTest.php
@@ -0,0 +1,614 @@
+conn = $this->createMock(PDO::class);
+ $this->pdoStatement = $this->createMock(PDOStatement::class);
+ $this->adminController = new AdminController($this->conn);
+ }
+
+ /** @test */
+ public function testGetDashboardData(): void
+ {
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->pdoStatement->method('fetch')->willReturn(['count' => 5]);
+ $this->pdoStatement->method('fetchAll')->willReturn([
+ ['id' => 1, 'total_price' => 100],
+ ['id' => 2, 'total_price' => 200]
+ ]);
+
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $result = $this->adminController->getDashboardData();
+
+ $this->assertIsArray($result);
+ $this->assertArrayHasKey('total_pendings', $result);
+ $this->assertArrayHasKey('total_completed', $result);
+ $this->assertArrayHasKey('orders_count', $result);
+ $this->assertArrayHasKey('products_count', $result);
+ }
+
+ /** @test */
+ public function testAddProductSuccessfully(): void
+ {
+ $postData = [
+ 'name' => 'Nuevo Producto',
+ 'price' => 99.99
+ ];
+
+ $files = [
+ 'image' => [
+ 'name' => 'test.jpg',
+ 'size' => 1000000,
+ 'tmp_name' => 'temp/test.jpg'
+ ]
+ ];
+
+ $this->pdoStatement->method('rowCount')->willReturn(0);
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $result = $this->adminController->addProduct($postData, $files);
+
+ $this->assertSame(true, $result['success']);
+ $this->assertSame('¡Producto añadido exitosamente!', $result['message']);
+ }
+
+ /** @test */
+ public function testUpdateOrderStatus(): void
+ {
+ $orderId = 1;
+ $status = 'completado';
+
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $result = $this->adminController->updateOrderStatus($orderId, $status);
+
+ $this->assertTrue($result);
+ }
+
+ /** @test */
+ public function testGetAllUsers(): void
+ {
+ $expectedUsers = [
+ [
+ 'id' => 1,
+ 'name' => 'Admin User',
+ 'email' => 'admin@test.com',
+ 'user_type' => 'admin'
+ ],
+ [
+ 'id' => 2,
+ 'name' => 'Normal User',
+ 'email' => 'user@test.com',
+ 'user_type' => 'user'
+ ]
+ ];
+
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->pdoStatement->method('fetchAll')->willReturn($expectedUsers);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $users = $this->adminController->getAllUsers();
+
+ $this->assertIsArray($users);
+ $this->assertCount(2, $users);
+ $this->assertInstanceOf(User::class, $users[0]);
+ $this->assertInstanceOf(User::class, $users[1]);
+ $this->assertEquals('Admin User', $users[0]->getName());
+ $this->assertEquals('user@test.com', $users[1]->getEmail());
+ }
+
+ /** @test */
+ public function testDeleteProduct(): void
+ {
+ $productId = 1;
+
+ $this->pdoStatement->method('fetch')->willReturn(['image' => 'test.jpg']);
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $result = $this->adminController->deleteProduct($productId);
+
+ $this->assertSame(true, $result['success']);
+ $this->assertSame('Producto eliminado', $result['message']);
+ }
+
+ /** @test */
+ public function testDeleteOrder(): void
+ {
+ $orderId = 1;
+
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $result = $this->adminController->deleteOrder($orderId);
+
+ $this->assertTrue($result);
+ }
+
+ /** @test */
+ public function testDeleteUser(): void
+ {
+ $userId = 1;
+
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $result = $this->adminController->deleteUser($userId);
+
+ $this->assertTrue($result);
+ }
+
+ /** @test */
+ public function testGetAllMessages(): void
+ {
+ $expectedMessages = [
+ [
+ 'id' => 1,
+ 'user_id' => 1,
+ 'message' => 'Test message',
+ 'name' => 'User Test',
+ 'email' => 'test@test.com',
+ 'number' => '123456789'
+ ]
+ ];
+
+ $this->pdoStatement->method('fetchAll')->willReturn($expectedMessages);
+ $this->conn->method('query')->willReturn($this->pdoStatement);
+
+ $messages = $this->adminController->getAllMessages();
+
+ $this->assertIsArray($messages);
+ $this->assertCount(1, $messages);
+ $this->assertEquals('Test message', $messages[0]->getMessage());
+ $this->assertEquals('test@test.com', $messages[0]->getEmail());
+ }
+
+ /** @test */
+ public function testDeleteMessage(): void
+ {
+ $messageId = 1;
+
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $result = $this->adminController->deleteMessage($messageId);
+
+ $this->assertTrue($result);
+ }
+
+ /** @test */
+ public function testGetAllProducts(): void
+ {
+ $expectedProducts = [
+ [
+ 'id' => 1,
+ 'name' => 'Product 1',
+ 'price' => 99.99,
+ 'image' => 'product1.jpg'
+ ]
+ ];
+
+ $this->pdoStatement->method('fetchAll')->willReturn($expectedProducts);
+ $this->conn->method('query')->willReturn($this->pdoStatement);
+
+ $products = $this->adminController->getAllProducts();
+
+ $this->assertIsArray($products);
+ $this->assertCount(1, $products);
+ $this->assertEquals('Product 1', $products[0]->getName());
+ $this->assertEquals(99.99, $products[0]->getPrice());
+ }
+
+ /** @test */
+ public function testUpdateProduct(): void
+ {
+ $postData = [
+ 'update_p_id' => 1,
+ 'update_name' => 'Updated Product',
+ 'update_price' => 149.99,
+ 'update_old_image' => 'old.jpg'
+ ];
+
+ $files = [
+ 'update_image' => [
+ 'name' => 'new.jpg',
+ 'size' => 1000000,
+ 'tmp_name' => 'temp/new.jpg'
+ ]
+ ];
+
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $result = $this->adminController->updateProduct($postData, $files);
+
+ $this->assertTrue($result['success']);
+ $this->assertEquals('Producto actualizado exitosamente', $result['message']);
+ }
+
+ /** @test */
+ public function testGetAllOrders(): void
+ {
+ $expectedOrders = [
+ [
+ 'id' => 1,
+ 'user_id' => 1,
+ 'placed_on' => '2024-03-20',
+ 'name' => 'Customer 1',
+ 'number' => '123456789',
+ 'email' => 'customer@test.com',
+ 'address' => 'Test Address',
+ 'total_products' => 2,
+ 'total_price' => 199.98,
+ 'method' => 'credit card',
+ 'payment_status' => 'completed'
+ ]
+ ];
+
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->pdoStatement->method('fetchAll')->willReturn($expectedOrders);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $orders = $this->adminController->getAllOrders();
+
+ $this->assertIsArray($orders);
+ $this->assertCount(1, $orders);
+ $this->assertEquals('Customer 1', $orders[0]->getName());
+ $this->assertEquals(199.98, $orders[0]->getTotalPrice());
+ }
+
+ /** @test */
+ public function testHandleDatabaseError(): void
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('Error al procesar la solicitud');
+
+ $reflection = new \ReflectionClass($this->adminController);
+ $method = $reflection->getMethod('handleDatabaseError');
+ $method->setAccessible(true);
+
+ $method->invoke($this->adminController, new \Exception('Test error'));
+ }
+
+
+ /** @test */
+ public function testGetTotalPendings(): void
+ {
+ $expectedOrders = [
+ [
+ 'id' => 1,
+ 'total_price' => 100.00
+ ],
+ [
+ 'id' => 2,
+ 'total_price' => 150.00
+ ]
+ ];
+
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->pdoStatement->method('fetchAll')->willReturn($expectedOrders);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $reflection = new \ReflectionClass($this->adminController);
+ $method = $reflection->getMethod('getTotalPendings');
+ $method->setAccessible(true);
+
+ $result = $method->invoke($this->adminController);
+
+ $this->assertEquals(250.00, $result);
+ }
+
+ /** @test */
+ public function testGetTotalCompleted(): void
+ {
+ $expectedOrders = [
+ [
+ 'id' => 1,
+ 'total_price' => 200.00
+ ],
+ [
+ 'id' => 2,
+ 'total_price' => 300.00
+ ]
+ ];
+
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->pdoStatement->method('fetchAll')->willReturn($expectedOrders);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $reflection = new \ReflectionClass($this->adminController);
+ $method = $reflection->getMethod('getTotalCompleted');
+ $method->setAccessible(true);
+
+ $result = $method->invoke($this->adminController);
+
+ $this->assertEquals(500.00, $result);
+ }
+
+ /** @test */
+ public function testGetOrdersCount(): void
+ {
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->pdoStatement->method('fetch')->willReturn(['count' => 10]);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $reflection = new \ReflectionClass($this->adminController);
+ $method = $reflection->getMethod('getOrdersCount');
+ $method->setAccessible(true);
+
+ $result = $method->invoke($this->adminController);
+
+ $this->assertEquals(10, $result);
+ }
+
+ /** @test */
+ public function testGetProductsCount(): void
+ {
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->pdoStatement->method('fetch')->willReturn(['count' => 15]);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $reflection = new \ReflectionClass($this->adminController);
+ $method = $reflection->getMethod('getProductsCount');
+ $method->setAccessible(true);
+
+ $result = $method->invoke($this->adminController);
+
+ $this->assertEquals(15, $result);
+ }
+
+ /** @test */
+ public function testGetUsersCount(): void
+ {
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->pdoStatement->method('fetch')->willReturn(['count' => 20]);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $reflection = new \ReflectionClass($this->adminController);
+ $method = $reflection->getMethod('getUsersCount');
+ $method->setAccessible(true);
+
+ $result = $method->invoke($this->adminController);
+
+ $this->assertEquals(20, $result);
+ }
+
+ /** @test */
+ public function testGetAdminsCount(): void
+ {
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->pdoStatement->method('fetch')->willReturn(['count' => 5]);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $reflection = new \ReflectionClass($this->adminController);
+ $method = $reflection->getMethod('getAdminsCount');
+ $method->setAccessible(true);
+
+ $result = $method->invoke($this->adminController);
+
+ $this->assertEquals(5, $result);
+ }
+
+ /** @test */
+ public function testGetTotalAccounts(): void
+ {
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->pdoStatement->method('fetch')->willReturn(['count' => 25]);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $reflection = new \ReflectionClass($this->adminController);
+ $method = $reflection->getMethod('getTotalAccounts');
+ $method->setAccessible(true);
+
+ $result = $method->invoke($this->adminController);
+
+ $this->assertEquals(25, $result);
+ }
+
+ /** @test */
+ public function testGetMessagesCount(): void
+ {
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->pdoStatement->method('fetch')->willReturn(['count' => 30]);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $reflection = new \ReflectionClass($this->adminController);
+ $method = $reflection->getMethod('getMessagesCount');
+ $method->setAccessible(true);
+
+ $result = $method->invoke($this->adminController);
+
+ $this->assertEquals(30, $result);
+ }
+
+ /** @test */
+ public function testUpdateProductWithoutImage(): void
+ {
+ $postData = [
+ 'update_p_id' => 1,
+ 'update_name' => 'Updated Product',
+ 'update_price' => 149.99
+ ];
+
+ $files = [
+ 'update_image' => [
+ 'name' => ''
+ ]
+ ];
+
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $result = $this->adminController->updateProduct($postData, $files);
+
+ $this->assertTrue($result['success']);
+ $this->assertEquals('Producto actualizado exitosamente', $result['message']);
+ }
+
+ /** @test */
+ public function testUpdateProductWithLargeImage(): void
+ {
+ $postData = [
+ 'update_p_id' => 1,
+ 'update_name' => 'Updated Product',
+ 'update_price' => 149.99
+ ];
+
+ $files = [
+ 'update_image' => [
+ 'name' => 'large.jpg',
+ 'size' => 3000000
+ ]
+ ];
+
+ $result = $this->adminController->updateProduct($postData, $files);
+
+ $this->assertFalse($result['success']);
+ $this->assertEquals('El tamaño de la imagen es demasiado grande', $result['message']);
+ }
+
+ /** @test */
+ public function testGetSecureImagePath(): void
+ {
+ $imageName = 'test.jpg';
+
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->pdoStatement->method('fetch')->willReturn(['image' => $imageName]);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $reflection = new \ReflectionClass($this->adminController);
+ $method = $reflection->getMethod('getSecureImagePath');
+ $method->setAccessible(true);
+
+ $result = $method->invoke($this->adminController, $imageName);
+
+ $this->assertNotFalse($result);
+ $this->assertStringContainsString($imageName, $result);
+ }
+
+ /** @test */
+ public function testGetSecureImagePathInvalidExtension(): void
+ {
+ $imageName = 'test.exe';
+
+ $reflection = new \ReflectionClass($this->adminController);
+ $method = $reflection->getMethod('getSecureImagePath');
+ $method->setAccessible(true);
+
+ $result = $method->invoke($this->adminController, $imageName);
+
+ $this->assertFalse($result);
+ }
+
+ /** @test */
+ public function testHandleImageDelete(): void
+ {
+ $imageName = 'test.jpg';
+
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->pdoStatement->method('fetch')->willReturn(['image' => $imageName]);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $reflection = new \ReflectionClass($this->adminController);
+ $method = $reflection->getMethod('handleImageDelete');
+ $method->setAccessible(true);
+
+ $result = $method->invoke($this->adminController, $imageName);
+
+ $this->assertIsBool($result);
+ }
+
+ /** @test */
+ public function testHandleImageDeleteInvalidName(): void
+ {
+ $reflection = new \ReflectionClass($this->adminController);
+ $method = $reflection->getMethod('handleImageDelete');
+ $method->setAccessible(true);
+
+ $result = $method->invoke($this->adminController, '');
+
+ $this->assertFalse($result);
+ }
+
+ /** @test */
+ public function testAddProductWithExistingName(): void
+ {
+ $postData = [
+ 'name' => 'Producto Existente',
+ 'price' => 99.99
+ ];
+
+ $files = [
+ 'image' => [
+ 'name' => 'test.jpg',
+ 'size' => 1000000,
+ 'tmp_name' => 'temp/test.jpg'
+ ]
+ ];
+
+ $this->pdoStatement->method('rowCount')->willReturn(1);
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $result = $this->adminController->addProduct($postData, $files);
+
+ $this->assertFalse($result['success']);
+ $this->assertEquals('El producto ya existe', $result['message']);
+ }
+
+ /** @test */
+ public function testUpdateProductDatabaseError(): void
+ {
+ $postData = [
+ 'update_p_id' => 1,
+ 'update_name' => 'Updated Product',
+ 'update_price' => 149.99
+ ];
+
+ $files = [
+ 'update_image' => [
+ 'name' => ''
+ ]
+ ];
+
+ $this->pdoStatement->method('execute')->willReturn(false);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $result = $this->adminController->updateProduct($postData, $files);
+
+ $this->assertFalse($result['success']);
+ $this->assertEquals('Error al actualizar el producto', $result['message']);
+ }
+
+ /** @test */
+ public function testUpdateOrderStatusDatabaseError(): void
+ {
+ $orderId = 1;
+ $status = 'completado';
+
+ $this->pdoStatement->method('execute')->willReturn(false);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $result = $this->adminController->updateOrderStatus($orderId, $status);
+
+ $this->assertFalse($result);
+ }
+}
diff --git a/tests/Unit/Controllers/ContactControllerTest.php b/tests/Unit/Controllers/ContactControllerTest.php
new file mode 100644
index 00000000..5ec67530
--- /dev/null
+++ b/tests/Unit/Controllers/ContactControllerTest.php
@@ -0,0 +1,259 @@
+conn = $this->createMock(PDO::class);
+ $this->pdoStatement = $this->createMock(PDOStatement::class);
+
+ $this->user = $this->createMock(User::class);
+ $this->message = $this->createMock(Message::class);
+
+ $this->contactController = $this->getMockBuilder(ContactController::class)
+ ->setConstructorArgs([$this->conn])
+ ->onlyMethods(['createUser', 'createMessage'])
+ ->getMock();
+
+ $this->contactController->method('createUser')
+ ->willReturn($this->user);
+ $this->contactController->method('createMessage')
+ ->willReturn($this->message);
+ }
+
+ /** @test */
+ public function enviar_mensaje_exitoso(): void
+ {
+ $datosUsuario = [
+ 'user_id' => 1,
+ 'name' => 'Juan Pérez',
+ 'email' => 'juan@example.com',
+ 'number' => '123456789',
+ 'message' => 'Mensaje de prueba'
+ ];
+
+ $this->user->method('exists')->willReturn(true);
+ $this->message->method('exists')->willReturn(false);
+ $this->message->method('save')->willReturn(true);
+
+ $resultado = $this->contactController->sendMessage($datosUsuario);
+
+ $this->assertSame(true, $resultado['success']);
+ $this->assertSame('¡Mensaje enviado exitosamente!', $resultado['message']);
+ }
+
+ /** @test */
+ public function enviar_mensaje_campos_faltantes(): void
+ {
+ $datosUsuario = [
+ 'user_id' => 1,
+ 'name' => 'Juan Pérez'
+ ];
+
+ $resultado = $this->contactController->sendMessage($datosUsuario);
+
+ $this->assertSame(false, $resultado['success']);
+ $this->assertSame('Faltan campos requeridos', $resultado['message']);
+ }
+
+ /** @test */
+ public function enviar_mensaje_usuario_no_encontrado(): void
+ {
+ $userData = [
+ 'user_id' => 999,
+ 'name' => 'Juan Pérez',
+ 'email' => 'juan@example.com',
+ 'number' => '123456789',
+ 'message' => 'Mensaje de prueba'
+ ];
+
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->pdoStatement->method('fetch')->willReturn(false);
+ $this->conn->method('prepare')->willReturn($this->pdoStatement);
+
+ $result = $this->contactController->sendMessage($userData);
+
+ $this->assertSame(false, $result['success']);
+ $this->assertSame('Usuario no encontrado', $result['message']);
+ }
+
+ /** @test */
+ public function enviar_mensaje_ya_enviado(): void
+ {
+ $userData = [
+ 'user_id' => 1,
+ 'name' => 'Juan Pérez',
+ 'email' => 'juan@example.com',
+ 'number' => '123456789',
+ 'message' => 'Mensaje de prueba'
+ ];
+
+ $this->user->method('exists')->willReturn(true);
+ $this->message->method('exists')->willReturn(true);
+
+ $result = $this->contactController->sendMessage($userData);
+
+ $this->assertSame(false, $result['success']);
+ $this->assertSame('¡Mensaje ya enviado!', $result['message']);
+ }
+
+ /** @test */
+ public function enviar_mensaje_error_al_guardar(): void
+ {
+ $userData = [
+ 'user_id' => 1,
+ 'name' => 'Juan Pérez',
+ 'email' => 'juan@example.com',
+ 'number' => '123456789',
+ 'message' => 'Mensaje de prueba'
+ ];
+
+ $this->user->method('exists')->willReturn(true);
+ $this->message->method('exists')->willReturn(false);
+ $this->message->method('save')->willReturn(false);
+
+ $result = $this->contactController->sendMessage($userData);
+
+ $this->assertSame(false, $result['success']);
+ $this->assertSame('Error al enviar mensaje', $result['message']);
+ }
+
+ /** @test */
+ public function enviar_mensaje_lanza_excepcion(): void
+ {
+ $userData = [
+ 'user_id' => 1,
+ 'name' => 'Juan Pérez',
+ 'email' => 'juan@example.com',
+ 'number' => '123456789',
+ 'message' => 'Mensaje de prueba'
+ ];
+
+ $this->user->method('exists')
+ ->willThrowException(new \Exception('Error de conexión'));
+
+ $result = $this->contactController->sendMessage($userData);
+
+ $this->assertSame(false, $result['success']);
+ $this->assertStringContainsString('Error al enviar mensaje', $result['message']);
+ }
+
+ /** @test */
+ public function crear_usuario_retorna_instancia_user(): void
+ {
+ $contactController = new ContactController($this->conn);
+ $reflection = new \ReflectionClass($contactController);
+ $method = $reflection->getMethod('createUser');
+ $method->setAccessible(true);
+
+ $result = $method->invoke($contactController);
+
+ $this->assertInstanceOf(User::class, $result);
+ }
+
+ /** @test */
+ public function crear_mensaje_retorna_instancia_message(): void
+ {
+ $contactController = new ContactController($this->conn);
+ $reflection = new \ReflectionClass($contactController);
+ $method = $reflection->getMethod('createMessage');
+ $method->setAccessible(true);
+
+ $result = $method->invoke($contactController);
+
+ $this->assertInstanceOf(Message::class, $result);
+ }
+
+ /** @test */
+ public function enviar_mensaje_valida_setters(): void
+ {
+ $userData = [
+ 'user_id' => 1,
+ 'name' => 'Juan Pérez',
+ 'email' => 'juan@example.com',
+ 'number' => '123456789',
+ 'message' => 'Mensaje de prueba'
+ ];
+
+ $this->message->expects($this->once())->method('setUserId')->with($userData['user_id']);
+ $this->message->expects($this->once())->method('setName')->with($userData['name']);
+ $this->message->expects($this->once())->method('setEmail')->with($userData['email']);
+ $this->message->expects($this->once())->method('setNumber')->with($userData['number']);
+ $this->message->expects($this->once())->method('setMessage')->with($userData['message']);
+
+ $this->user->method('exists')->willReturn(true);
+ $this->message->method('exists')->willReturn(false);
+ $this->message->method('save')->willReturn(true);
+
+ $this->contactController->sendMessage($userData);
+ }
+
+ /** @test */
+ public function constructor_asigna_conexion(): void
+ {
+ $contactController = new ContactController($this->conn);
+ $reflection = new \ReflectionClass($contactController);
+ $property = $reflection->getProperty('conn');
+ $property->setAccessible(true);
+
+ $this->assertSame($this->conn, $property->getValue($contactController));
+ }
+
+ /** @test */
+ public function enviar_mensaje_maneja_error_en_save(): void
+ {
+ $userData = [
+ 'user_id' => 1,
+ 'name' => 'Juan Pérez',
+ 'email' => 'juan@example.com',
+ 'number' => '123456789',
+ 'message' => 'Mensaje de prueba'
+ ];
+
+ $this->user->method('exists')->willReturn(true);
+ $this->message->method('exists')->willReturn(false);
+ $this->message->method('save')->willThrowException(new \Exception('Error al guardar'));
+
+ $result = $this->contactController->sendMessage($userData);
+
+ $this->assertFalse($result['success']);
+ $this->assertStringContainsString('Error al enviar mensaje', $result['message']);
+ }
+
+ /** @test */
+ public function enviar_mensaje_valida_user_id(): void
+ {
+ $userData = [
+ 'user_id' => 1,
+ 'name' => 'Juan Pérez',
+ 'email' => 'juan@example.com',
+ 'number' => '123456789',
+ 'message' => 'Mensaje de prueba'
+ ];
+
+ $this->user->expects($this->once())
+ ->method('setId')
+ ->with($userData['user_id']);
+
+ $this->user->method('exists')->willReturn(true);
+ $this->message->method('exists')->willReturn(false);
+ $this->message->method('save')->willReturn(true);
+
+ $this->contactController->sendMessage($userData);
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Controllers/OrderControllerTest.php b/tests/Unit/Controllers/OrderControllerTest.php
new file mode 100644
index 00000000..3cb4cf2f
--- /dev/null
+++ b/tests/Unit/Controllers/OrderControllerTest.php
@@ -0,0 +1,391 @@
+conn = $this->createMock(PDO::class);
+ $this->pdoStatement = $this->createMock(PDOStatement::class);
+ $this->orderController = new OrderController($this->conn);
+ }
+
+ /** @test */
+ public function crear_pedido(): void
+ {
+ $userId = 1;
+ $userData = [
+ 'name' => 'Juan Pérez',
+ 'number' => '123456789',
+ 'email' => 'juan@example.com',
+ 'method' => 'credit card',
+ 'flat' => '123',
+ 'street' => 'Calle Principal',
+ 'city' => 'Lima',
+ 'country' => 'Perú',
+ 'pin_code' => '12345'
+ ];
+
+ $cartItems = [
+ [
+ 'name' => 'Producto 1',
+ 'quantity' => 2,
+ 'price' => 100
+ ]
+ ];
+
+ $this->conn->expects($this->exactly(4))
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->exactly(4))
+ ->method('execute')
+ ->willReturn(true);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('rowCount')
+ ->willReturn(0);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('fetchAll')
+ ->willReturn($cartItems);
+
+ $result = $this->orderController->createOrder($userData, $userId);
+
+ $this->assertSame(true, $result['success']);
+ $this->assertSame('¡Pedido realizado con éxito!', $result['message']);
+ }
+
+ /** @test */
+ public function obtener_pedidos_usuario(): void
+ {
+ $userId = 1;
+ $expectedOrders = [
+ [
+ 'user_id' => 1,
+ 'name' => 'Juan Pérez',
+ 'number' => '123456789',
+ 'email' => 'juan@example.com',
+ 'method' => 'credit card',
+ 'address' => 'Dirección de prueba',
+ 'total_products' => 'Producto 1 (2)',
+ 'total_price' => 200,
+ 'payment_status' => 'pending',
+ 'placed_on' => '20-Mar-2024'
+ ]
+ ];
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute')
+ ->with([$userId]);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('fetchAll')
+ ->willReturn($expectedOrders);
+
+ $result = $this->orderController->getUserOrders($userId);
+
+ $this->assertNotEmpty($result);
+ $this->assertIsArray($result);
+ $this->assertSame($userId, $result[0]->getUserId());
+ }
+
+ /** @test */
+ public function actualizar_estado_pago(): void
+ {
+ $orderId = 1;
+ $status = 'completed';
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute')
+ ->with([$status, $orderId])
+ ->willReturn(true);
+
+ $result = $this->orderController->updatePaymentStatus($orderId, $status);
+ $this->assertTrue($result);
+ }
+
+ /** @test */
+ public function eliminar_pedido(): void
+ {
+ $orderId = 1;
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute')
+ ->with([$orderId])
+ ->willReturn(true);
+
+ $result = $this->orderController->deleteOrder($orderId);
+ $this->assertTrue($result);
+ }
+
+ /** @test */
+ public function obtener_todos_pedidos(): void
+ {
+ $expectedOrders = [
+ [
+ 'user_id' => 1,
+ 'name' => 'Juan Pérez',
+ 'email' => 'juan@example.com',
+ 'method' => 'credit card',
+ 'address' => 'Dirección de prueba',
+ 'total_products' => 'Producto 1 (2)',
+ 'total_price' => 200
+ ]
+ ];
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute');
+
+ $this->pdoStatement->expects($this->once())
+ ->method('fetchAll')
+ ->willReturn($expectedOrders);
+
+ $result = $this->orderController->getAllOrders();
+
+ $this->assertNotEmpty($result);
+ $this->assertIsArray($result);
+ $this->assertSame($expectedOrders[0]['user_id'], $result[0]->getUserId());
+ }
+
+ /** @test */
+ public function manejar_error_base_datos_en_obtener_pedidos(): void
+ {
+ $userId = 1;
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willThrowException(new \Exception('Database error'));
+
+ $result = $this->orderController->getOrders($userId);
+ $this->assertEmpty($result);
+ }
+
+ public function manejar_error_base_datos_en_actualizar_estado(): void
+ {
+ $orderId = 1;
+ $status = 'completed';
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willThrowException(new \Exception('Database error'));
+
+ $result = $this->orderController->updatePaymentStatus($orderId, $status);
+ $this->assertFalse($result);
+ }
+
+ /** @test */
+ public function obtener_todos_productos(): void
+ {
+ $expectedProducts = [
+ [
+ 'name' => 'Producto 1',
+ 'price' => 100,
+ 'image' => 'imagen1.jpg'
+ ],
+ [
+ 'name' => 'Producto 2',
+ 'price' => 200,
+ 'image' => 'imagen2.jpg'
+ ]
+ ];
+
+ $this->conn->expects($this->once())
+ ->method('query')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('fetchAll')
+ ->willReturn($expectedProducts);
+
+ $result = $this->orderController->getAllProducts();
+
+ $this->assertNotEmpty($result);
+ $this->assertCount(2, $result);
+ $this->assertEquals('Producto 1', $result[0]->getName());
+ }
+
+ /** @test */
+ public function obtener_todos_usuarios(): void
+ {
+ $expectedUsers = [
+ [
+ 'name' => 'Usuario 1',
+ 'email' => 'usuario1@test.com',
+ 'user_type' => 'user'
+ ],
+ [
+ 'name' => 'Admin',
+ 'email' => 'admin@test.com',
+ 'user_type' => 'admin'
+ ]
+ ];
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute');
+
+ $this->pdoStatement->expects($this->once())
+ ->method('fetchAll')
+ ->willReturn($expectedUsers);
+
+ $result = $this->orderController->getAllUsers();
+
+ $this->assertNotEmpty($result);
+ $this->assertCount(2, $result);
+ $this->assertEquals('Usuario 1', $result[0]->getName());
+ }
+
+ /** @test */
+ public function crear_pedido_con_carrito_vacio(): void
+ {
+ $userId = 1;
+ $userData = [
+ 'name' => 'Juan Pérez',
+ 'number' => '123456789',
+ 'email' => 'juan@test.com',
+ 'method' => 'credit card',
+ 'flat' => '123',
+ 'street' => 'Calle Principal',
+ 'city' => 'Lima',
+ 'country' => 'Perú',
+ 'pin_code' => '12345'
+ ];
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('fetchAll')
+ ->willReturn([]);
+
+ $result = $this->orderController->createOrder($userData, $userId);
+
+ $this->assertFalse($result['success']);
+ $this->assertEquals('El carrito está vacío', $result['message']);
+ }
+
+ /** @test */
+ public function crear_pedido_duplicado(): void
+ {
+ $userId = 1;
+ $userData = [
+ 'name' => 'Juan Pérez',
+ 'number' => '123456789',
+ 'email' => 'juan@test.com',
+ 'method' => 'credit card',
+ 'flat' => '123',
+ 'street' => 'Calle Principal',
+ 'city' => 'Lima',
+ 'country' => 'Perú',
+ 'pin_code' => '12345'
+ ];
+
+ $cartItems = [
+ [
+ 'name' => 'Producto 1',
+ 'quantity' => 2,
+ 'price' => 100
+ ]
+ ];
+
+ $this->conn->expects($this->exactly(2))
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('fetchAll')
+ ->willReturn($cartItems);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('rowCount')
+ ->willReturn(1);
+
+ $result = $this->orderController->createOrder($userData, $userId);
+
+ $this->assertFalse($result['success']);
+ $this->assertEquals('¡Pedido ya realizado!', $result['message']);
+ }
+
+ /** @test */
+ public function manejar_error_al_crear_pedido(): void
+ {
+ $userId = 1;
+ $userData = [
+ 'name' => 'Juan Pérez',
+ 'number' => '123456789',
+ 'email' => 'juan@test.com',
+ 'method' => 'credit card',
+ 'flat' => '123',
+ 'street' => 'Calle Principal',
+ 'city' => 'Lima',
+ 'country' => 'Perú',
+ 'pin_code' => '12345'
+ ];
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willThrowException(new \Exception('Error de base de datos'));
+
+ $result = $this->orderController->createOrder($userData, $userId);
+
+ $this->assertFalse($result['success']);
+ $this->assertEquals('Error al procesar el pedido', $result['message']);
+ }
+
+ /** @test */
+ public function manejar_error_al_obtener_todos_productos(): void
+ {
+ $this->conn->expects($this->once())
+ ->method('query')
+ ->willThrowException(new \Exception('Error de base de datos'));
+
+ $result = $this->orderController->getAllProducts();
+
+ $this->assertEmpty($result);
+ $this->assertIsArray($result);
+ }
+
+ /** @test */
+ public function manejar_error_al_obtener_todos_usuarios(): void
+ {
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willThrowException(new \Exception('Error de base de datos'));
+
+ $result = $this->orderController->getAllUsers();
+
+ $this->assertEmpty($result);
+ $this->assertIsArray($result);
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Controllers/ProductControllerTest.php b/tests/Unit/Controllers/ProductControllerTest.php
new file mode 100644
index 00000000..b6b21956
--- /dev/null
+++ b/tests/Unit/Controllers/ProductControllerTest.php
@@ -0,0 +1,363 @@
+conn = $this->createMock(PDO::class);
+ $this->pdoStatement = $this->createMock(PDOStatement::class);
+ $this->productController = new ProductController($this->conn);
+ }
+
+ /** @test */
+ public function obtener_productos_recientes(): void
+ {
+ $expectedProducts = [
+ [
+ 'id' => 1,
+ 'name' => 'Producto Test',
+ 'price' => 99.99,
+ 'image' => 'test.jpg'
+ ]
+ ];
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute');
+
+ $this->pdoStatement->expects($this->once())
+ ->method('fetchAll')
+ ->willReturn($expectedProducts);
+
+ $result = $this->productController->getLatestProducts(1);
+
+ $this->assertIsArray($result);
+ $this->assertInstanceOf(Product::class, $result[0]);
+ $this->assertSame('Producto Test', $result[0]->getName());
+ }
+
+ /** @test */
+ public function agregar_al_carrito(): void
+ {
+ $userId = 1;
+ $productData = [
+ 'product_name' => 'Producto Test',
+ 'product_price' => 99.99,
+ 'product_quantity' => 1,
+ 'product_image' => 'test.jpg'
+ ];
+
+ $this->pdoStatement->method('rowCount')->willReturn(0);
+
+ $this->conn->expects($this->exactly(2))
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->exactly(2))
+ ->method('execute')
+ ->willReturn(true);
+
+ $result = $this->productController->addToCart($userId, $productData);
+
+ $this->assertSame(true, $result['success']);
+ $this->assertSame('Producto añadido al carrito', $result['message']);
+ }
+
+ /** @test */
+ public function obtener_items_carrito(): void
+ {
+ $userId = 1;
+ $expectedItems = [
+ [
+ 'id' => 1,
+ 'name' => 'Producto Test',
+ 'price' => 99.99,
+ 'quantity' => 1,
+ 'image' => 'test.jpg'
+ ]
+ ];
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute')
+ ->with([$userId]);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('fetchAll')
+ ->willReturn($expectedItems);
+
+ $result = $this->productController->getCartItems($userId);
+
+ $this->assertSame($expectedItems, $result);
+ }
+
+ /** @test */
+ public function actualizar_cantidad_carrito_exitoso(): void
+ {
+ $cartId = 1;
+ $quantity = 2;
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute')
+ ->with([$quantity, $cartId])
+ ->willReturn(true);
+
+ $result = $this->productController->updateCartQuantity($cartId, $quantity);
+
+ $this->assertTrue($result['success']);
+ $this->assertEquals('¡Cantidad actualizada!', $result['message']);
+ }
+
+ /** @test */
+ public function eliminar_item_carrito_exitoso(): void
+ {
+ $cartId = 1;
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute')
+ ->with([$cartId])
+ ->willReturn(true);
+
+ $result = $this->productController->deleteCartItem($cartId);
+
+ $this->assertTrue($result);
+ }
+
+ /** @test */
+ public function eliminar_todos_items_carrito_exitoso(): void
+ {
+ $userId = 1;
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute')
+ ->with([$userId])
+ ->willReturn(true);
+
+ $result = $this->productController->deleteAllCartItems($userId);
+
+ $this->assertTrue($result);
+ }
+
+ /** @test */
+ public function obtener_todos_productos(): void
+ {
+ $expectedProducts = [
+ [
+ 'id' => 1,
+ 'name' => 'Producto 1',
+ 'price' => 99.99,
+ 'image' => 'test1.jpg'
+ ],
+ [
+ 'id' => 2,
+ 'name' => 'Producto 2',
+ 'price' => 149.99,
+ 'image' => 'test2.jpg'
+ ]
+ ];
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute');
+
+ $this->pdoStatement->expects($this->once())
+ ->method('fetchAll')
+ ->willReturn($expectedProducts);
+
+ $result = $this->productController->getAllProducts();
+
+ $this->assertCount(2, $result);
+ $this->assertInstanceOf(Product::class, $result[0]);
+ $this->assertEquals('Producto 1', $result[0]->getName());
+ $this->assertEquals('Producto 2', $result[1]->getName());
+ }
+
+ /** @test */
+ public function agregar_producto_duplicado_al_carrito(): void
+ {
+ $userId = 1;
+ $productData = [
+ 'product_name' => 'Producto Test',
+ 'product_price' => 99.99,
+ 'product_quantity' => 1,
+ 'product_image' => 'test.jpg'
+ ];
+
+ $this->pdoStatement->method('rowCount')->willReturn(1);
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $result = $this->productController->addToCart($userId, $productData);
+
+ $this->assertFalse($result['success']);
+ $this->assertEquals('El producto ya está en el carrito', $result['message']);
+ }
+
+ /** @test */
+ public function manejo_error_al_actualizar_cantidad(): void
+ {
+ $cartId = 1;
+ $quantity = 2;
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute')
+ ->willThrowException(new \Exception('Error de base de datos'));
+
+ $result = $this->productController->updateCartQuantity($cartId, $quantity);
+
+ $this->assertFalse($result['success']);
+ $this->assertEquals('Error al actualizar cantidad', $result['message']);
+ }
+
+ /** @test */
+ public function manejo_error_al_obtener_productos_recientes(): void
+ {
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willThrowException(new \Exception('Error de conexión'));
+
+ $result = $this->productController->getLatestProducts();
+
+ $this->assertIsArray($result);
+ $this->assertEmpty($result);
+ }
+
+ /** @test */
+ public function manejo_error_al_obtener_todos_productos(): void
+ {
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willThrowException(new \Exception('Error de base de datos'));
+
+ $result = $this->productController->getAllProducts();
+
+ $this->assertIsArray($result);
+ $this->assertEmpty($result);
+ }
+
+ /** @test */
+ public function manejo_error_al_obtener_items_carrito(): void
+ {
+ $userId = 1;
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willThrowException(new \Exception('Error de conexión'));
+
+ $result = $this->productController->getCartItems($userId);
+
+ $this->assertIsArray($result);
+ $this->assertEmpty($result);
+ }
+
+ /** @test */
+ public function manejo_error_al_eliminar_item_carrito(): void
+ {
+ $cartId = 1;
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willThrowException(new \Exception('Error de base de datos'));
+
+ $result = $this->productController->deleteCartItem($cartId);
+
+ $this->assertFalse($result);
+ }
+
+ /** @test */
+ public function manejo_error_al_eliminar_todos_items_carrito(): void
+ {
+ $userId = 1;
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willThrowException(new \Exception('Error de base de datos'));
+
+ $result = $this->productController->deleteAllCartItems($userId);
+
+ $this->assertFalse($result);
+ }
+
+ /** @test */
+ public function manejo_error_al_agregar_al_carrito(): void
+ {
+ $userId = 1;
+ $productData = [
+ 'product_name' => 'Producto Test',
+ 'product_price' => 99.99,
+ 'product_quantity' => 1,
+ 'product_image' => 'test.jpg'
+ ];
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willThrowException(new \Exception('Error de base de datos'));
+
+ $result = $this->productController->addToCart($userId, $productData);
+
+ $this->assertFalse($result['success']);
+ $this->assertEquals('Error al añadir al carrito', $result['message']);
+ }
+
+ /** @test */
+ public function verificar_carrito_vacio(): void
+ {
+ $userId = 1;
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute')
+ ->with([$userId]);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('fetchAll')
+ ->willReturn([]);
+
+ $result = $this->productController->getCartItems($userId);
+
+ $this->assertIsArray($result);
+ $this->assertEmpty($result);
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Controllers/SearchControllerTest.php b/tests/Unit/Controllers/SearchControllerTest.php
new file mode 100644
index 00000000..da073494
--- /dev/null
+++ b/tests/Unit/Controllers/SearchControllerTest.php
@@ -0,0 +1,67 @@
+conn = $this->createMock(PDO::class);
+ $this->pdoStatement = $this->createMock(PDOStatement::class);
+ $this->searchController = new SearchController($this->conn);
+ }
+
+ /** @test */
+ public function buscar_productos_devuelve_resultados(): void
+ {
+ $resultadosEsperados = [
+ ['id' => 1, 'name' => 'Producto 1'],
+ ['id' => 2, 'name' => 'Producto 2']
+ ];
+
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->pdoStatement->method('fetchAll')->willReturn($resultadosEsperados);
+
+ $this->conn->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $resultados = $this->searchController->searchProducts('Producto');
+
+ $this->assertEquals($resultadosEsperados, $resultados);
+ }
+
+ /** @test */
+ public function buscar_productos_devuelve_array_vacio_cuando_no_hay_resultados(): void
+ {
+ $this->pdoStatement->method('execute')->willReturn(true);
+ $this->pdoStatement->method('fetchAll')->willReturn([]);
+
+ $this->conn->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $resultados = $this->searchController->searchProducts('NoExiste');
+
+ $this->assertEmpty($resultados);
+ }
+
+ /** @test */
+ public function buscar_productos_devuelve_array_vacio_cuando_hay_excepcion(): void
+ {
+ $this->conn->method('prepare')
+ ->willThrowException(new \Exception('Error de conexión'));
+
+ $resultados = $this->searchController->searchProducts('Producto');
+
+ $this->assertEmpty($resultados);
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Controllers/UserControllerTest.php b/tests/Unit/Controllers/UserControllerTest.php
new file mode 100644
index 00000000..37ba13fa
--- /dev/null
+++ b/tests/Unit/Controllers/UserControllerTest.php
@@ -0,0 +1,504 @@
+mockPDO = $this->createMock(\PDO::class);
+ $this->userController = new UserController($this->mockPDO);
+ }
+
+ /** @test */
+ public function usuario_puede_registrarse(): void
+ {
+ $mockStmt = $this->createMock(PDOStatement::class);
+ $mockStmt->method('fetch')->willReturn(false);
+ $mockStmt->method('execute')->willReturn(true);
+
+ $this->mockPDO->method('prepare')->willReturn($mockStmt);
+
+ $result = $this->userController->registerUser([
+ 'name' => 'Juan Pérez',
+ 'email' => 'juan@example.com',
+ 'password' => 'password123'
+ ]);
+
+ $this->assertSame(true, $result['success']);
+ $this->assertSame('Registro exitoso!', $result['message']);
+ }
+
+ /** @test */
+ public function usuario_puede_iniciar_sesion(): void
+ {
+ $password = 'password123';
+ $hashedPassword = password_hash($password, PASSWORD_BCRYPT);
+
+ $mockStmt = $this->createMock(\PDOStatement::class);
+ $mockStmt->method('fetch')->willReturn([
+ 'id' => 1,
+ 'name' => 'Juan Pérez',
+ 'email' => 'juan@example.com',
+ 'password' => $hashedPassword,
+ 'user_type' => 'user'
+ ]);
+ $mockStmt->method('execute')->willReturn(true);
+
+ $this->mockPDO->method('prepare')
+ ->willReturn($mockStmt);
+
+ $result = $this->userController->loginUser('juan@example.com', $password);
+
+ $this->assertTrue($result['success']);
+ $this->assertEquals('user', $result['user_type']);
+ }
+
+ /** @test */
+ public function inicio_sesion_falla_con_credenciales_incorrectas(): void
+ {
+ $mockStmt = $this->createMock(PDOStatement::class);
+ $mockStmt->method('fetch')->willReturn(false);
+ $mockStmt->method('execute')->willReturn(true);
+
+ $this->mockPDO->method('prepare')->willReturn($mockStmt);
+
+ $result = $this->userController->loginUser('wrong@email.com', 'wrongpass');
+
+ $this->assertSame(false, $result['success']);
+ $this->assertSame('Correo o contraseña incorrectos', $result['message']);
+ }
+
+ /** @test */
+ public function puede_obtener_usuario_por_id(): void
+ {
+ $mockStmt = $this->createMock(PDOStatement::class);
+ $mockStmt->method('fetch')->willReturn([
+ 'id' => 1,
+ 'name' => 'Juan Pérez',
+ 'email' => 'juan@example.com',
+ 'user_type' => 'user'
+ ]);
+ $mockStmt->method('execute')->willReturn(true);
+
+ $this->mockPDO->method('prepare')->willReturn($mockStmt);
+
+ $user = $this->userController->getUserById(1);
+
+ $this->assertSame('Juan Pérez', $user['name']);
+ $this->assertSame('juan@example.com', $user['email']);
+ $this->assertSame('user', $user['user_type']);
+ }
+
+ /** @test */
+ public function registro_falla_con_email_existente(): void
+ {
+ $mockStmt = $this->createMock(PDOStatement::class);
+ $mockStmt->method('fetch')->willReturn(['id' => 1]);
+ $mockStmt->method('execute')->willReturn(true);
+
+ $this->mockPDO->method('prepare')->willReturn($mockStmt);
+
+ $result = $this->userController->registerUser([
+ 'name' => 'Juan Pérez',
+ 'email' => 'existente@example.com',
+ 'password' => 'password123'
+ ]);
+
+ $this->assertFalse($result['success']);
+ $this->assertEquals('El correo ya está registrado', $result['message']);
+ }
+
+ /** @test */
+ public function manejo_error_en_registro(): void
+ {
+ $mockStmt = $this->createMock(PDOStatement::class);
+ $mockStmt->method('execute')->willThrowException(new \Exception('Error de base de datos'));
+
+ $this->mockPDO->method('prepare')->willReturn($mockStmt);
+
+ $result = $this->userController->registerUser([
+ 'name' => 'Juan Pérez',
+ 'email' => 'juan@example.com',
+ 'password' => 'password123'
+ ]);
+
+ $this->assertFalse($result['success']);
+ $this->assertEquals('Error en el registro', $result['message']);
+ }
+
+ /** @test */
+ public function manejo_error_en_login(): void
+ {
+ $mockStmt = $this->createMock(PDOStatement::class);
+ $mockStmt->method('execute')->willThrowException(new \Exception('Error de base de datos'));
+
+ $this->mockPDO->method('prepare')->willReturn($mockStmt);
+
+ $result = $this->userController->loginUser('juan@example.com', 'password123');
+
+ $this->assertFalse($result['success']);
+ $this->assertEquals('Error en el inicio de sesión', $result['message']);
+ }
+
+ /** @test */
+ public function manejo_error_al_obtener_usuario(): void
+ {
+ $mockStmt = $this->createMock(PDOStatement::class);
+ $mockStmt->method('execute')->willThrowException(new \Exception('Error de base de datos'));
+
+ $this->mockPDO->method('prepare')->willReturn($mockStmt);
+
+ $result = $this->userController->getUserById(1);
+
+ $this->assertNull($result);
+ }
+
+ /** @test */
+ public function registro_con_tipo_usuario_personalizado(): void
+ {
+ $mockStmt = $this->createMock(PDOStatement::class);
+ $mockStmt->method('fetch')->willReturn(false);
+ $mockStmt->method('execute')->willReturn(true);
+
+ $this->mockPDO->method('prepare')->willReturn($mockStmt);
+
+ $result = $this->userController->registerUser([
+ 'name' => 'Admin User',
+ 'email' => 'admin@example.com',
+ 'password' => 'admin123',
+ 'user_type' => 'admin'
+ ]);
+
+ $this->assertTrue($result['success']);
+ $this->assertEquals('Registro exitoso!', $result['message']);
+ }
+
+ /** @test */
+ public function verificar_alias_register(): void
+ {
+ $mockStmt = $this->createMock(PDOStatement::class);
+ $mockStmt->method('fetch')->willReturn(false);
+ $mockStmt->method('execute')->willReturn(true);
+
+ $this->mockPDO->method('prepare')->willReturn($mockStmt);
+
+ $result = $this->userController->register([
+ 'name' => 'Juan Pérez',
+ 'email' => 'juan@example.com',
+ 'password' => 'password123'
+ ]);
+
+ $this->assertTrue($result['success']);
+ $this->assertEquals('Registro exitoso!', $result['message']);
+ }
+
+ /** @test */
+ public function verificar_hash_password(): void
+ {
+ $mockStmt = $this->createMock(PDOStatement::class);
+ $password = 'password123';
+
+ $this->mockPDO->method('prepare')->willReturn($mockStmt);
+
+ $result = $this->userController->registerUser([
+ 'name' => 'Test User',
+ 'email' => 'test@example.com',
+ 'password' => $password
+ ]);
+
+ $this->assertTrue($result['success']);
+ }
+
+ /** @test */
+ public function verifica_hash_password_con_costo_correcto(): void
+ {
+ $reflection = new \ReflectionClass($this->userController);
+ $method = $reflection->getMethod('hashPassword');
+ $method->setAccessible(true);
+
+ $password = 'test123';
+ $hashedPassword = $method->invoke($this->userController, $password);
+
+ $this->assertTrue(password_verify($password, $hashedPassword));
+ $this->assertStringContainsString('$2y$12$', $hashedPassword);
+ }
+
+ /** @test */
+ public function verifica_password_correcto(): void
+ {
+ $reflection = new \ReflectionClass($this->userController);
+ $method = $reflection->getMethod('verifyPassword');
+ $method->setAccessible(true);
+
+ $password = 'test123';
+ $hashedPassword = password_hash($password, PASSWORD_BCRYPT);
+
+ $result = $method->invoke($this->userController, $password, $hashedPassword);
+ $this->assertTrue($result);
+ }
+
+ /** @test */
+ public function verifica_password_incorrecto(): void
+ {
+ $reflection = new \ReflectionClass($this->userController);
+ $method = $reflection->getMethod('verifyPassword');
+ $method->setAccessible(true);
+
+ $password = 'test123';
+ $wrongPassword = 'wrong123';
+ $hashedPassword = password_hash($password, PASSWORD_BCRYPT);
+
+ $result = $method->invoke($this->userController, $wrongPassword, $hashedPassword);
+ $this->assertFalse($result);
+ }
+
+ /** @test */
+ public function login_falla_con_password_incorrecto(): void
+ {
+ $mockStmt = $this->createMock(PDOStatement::class);
+ $mockStmt->method('fetch')->willReturn([
+ 'id' => 1,
+ 'password' => password_hash('correctpass', PASSWORD_BCRYPT),
+ 'user_type' => 'user'
+ ]);
+ $mockStmt->method('execute')->willReturn(true);
+
+ $this->mockPDO->method('prepare')->willReturn($mockStmt);
+
+ $result = $this->userController->loginUser('test@example.com', 'wrongpass');
+
+ $this->assertFalse($result['success']);
+ $this->assertEquals('Correo o contraseña incorrectos', $result['message']);
+ }
+
+ /** @test */
+ public function login_exitoso_con_admin(): void
+ {
+ $password = 'admin123';
+ $hashedPassword = password_hash($password, PASSWORD_BCRYPT);
+
+ $mockStmt = $this->createMock(\PDOStatement::class);
+ $mockStmt->method('fetch')->willReturn([
+ 'id' => 1,
+ 'name' => 'Admin',
+ 'email' => 'admin@example.com',
+ 'password' => $hashedPassword,
+ 'user_type' => 'admin'
+ ]);
+ $mockStmt->method('execute')->willReturn(true);
+
+ $this->mockPDO->method('prepare')
+ ->willReturn($mockStmt);
+
+ $result = $this->userController->loginUser('admin@example.com', $password);
+
+ $this->assertTrue($result['success']);
+ $this->assertEquals('admin', $result['user_type']);
+ }
+
+ /** @test */
+ public function registro_exitoso_sin_tipo_usuario(): void
+ {
+ $mockStmt = $this->createMock(PDOStatement::class);
+ $mockStmt->method('fetch')->willReturn(false);
+ $mockStmt->method('execute')->willReturn(true);
+
+ $this->mockPDO->method('prepare')->willReturn($mockStmt);
+
+ $result = $this->userController->registerUser([
+ 'name' => 'Default User',
+ 'email' => 'default@test.com',
+ 'password' => 'pass123'
+ ]);
+
+ $this->assertTrue($result['success']);
+ $this->assertEquals('Registro exitoso!', $result['message']);
+ }
+
+ /** @test */
+ public function obtener_usuario_inexistente(): void
+ {
+ $mockStmt = $this->createMock(PDOStatement::class);
+ $mockStmt->method('execute')->willReturn(true);
+ $mockStmt->method('fetch')->willReturn(false);
+
+ $this->mockPDO->method('prepare')->willReturn($mockStmt);
+
+ $result = $this->userController->getUserById(999);
+ $this->assertFalse($result);
+ }
+
+ public function test_register_con_email_existente()
+ {
+ $stmt = $this->createMock(\PDOStatement::class);
+ $stmt->method('fetch')->willReturn(['id' => 1]);
+ $stmt->method('execute')->willReturn(true);
+
+ $this->mockPDO->method('prepare')->willReturn($stmt);
+
+ $resultado = $this->userController->registerUser([
+ 'name' => 'Test User',
+ 'email' => 'existente@test.com',
+ 'password' => '123456'
+ ]);
+
+ $this->assertFalse($resultado['success']);
+ $this->assertEquals('El correo ya está registrado', $resultado['message']);
+ }
+
+ public function test_register_con_error_db()
+ {
+ $stmt = $this->createMock(\PDOStatement::class);
+ $stmt->method('execute')->willThrowException(new \PDOException('Error de conexión'));
+
+ $this->mockPDO->method('prepare')->willReturn($stmt);
+
+ $resultado = $this->userController->registerUser([
+ 'name' => 'Test User',
+ 'email' => 'test@test.com',
+ 'password' => '123456'
+ ]);
+
+ $this->assertFalse($resultado['success']);
+ $this->assertEquals('Error en el registro', $resultado['message']);
+ }
+
+ // Pruebas de login con errores
+ public function test_login_con_credenciales_invalidas()
+ {
+ $stmt = $this->createMock(\PDOStatement::class);
+ $stmt->method('fetch')->willReturn(false);
+ $stmt->method('execute')->willReturn(true);
+
+ $this->mockPDO->method('prepare')->willReturn($stmt);
+
+ $resultado = $this->userController->loginUser('noexiste@test.com', '123456');
+
+ $this->assertFalse($resultado['success']);
+ $this->assertEquals('Correo o contraseña incorrectos', $resultado['message']);
+ }
+
+ public function test_login_con_password_incorrecto()
+ {
+ $hashedPassword = password_hash('password_correcto', PASSWORD_BCRYPT);
+
+ $stmt = $this->createMock(\PDOStatement::class);
+ $stmt->method('fetch')->willReturn([
+ 'id' => 1,
+ 'password' => $hashedPassword,
+ 'name' => 'Test User',
+ 'user_type' => 'user'
+ ]);
+ $stmt->method('execute')->willReturn(true);
+
+ $this->mockPDO->method('prepare')->willReturn($stmt);
+
+ $resultado = $this->userController->loginUser('test@test.com', 'password_incorrecto');
+
+ $this->assertFalse($resultado['success']);
+ $this->assertEquals('Correo o contraseña incorrectos', $resultado['message']);
+ }
+
+ public function test_login_con_error_db()
+ {
+ $stmt = $this->createMock(\PDOStatement::class);
+ $stmt->method('execute')->willThrowException(new \PDOException('Error de conexión'));
+
+ $this->mockPDO->method('prepare')->willReturn($stmt);
+
+ $resultado = $this->userController->loginUser('test@test.com', '123456');
+
+ $this->assertFalse($resultado['success']);
+ $this->assertEquals('Error en el inicio de sesión', $resultado['message']);
+ }
+
+ // Pruebas de getUserById con errores
+ public function test_getUserById_usuario_no_existe()
+ {
+ $stmt = $this->createMock(\PDOStatement::class);
+ $stmt->method('fetch')->willReturn(false);
+ $stmt->method('execute')->willReturn(true);
+
+ $this->mockPDO->method('prepare')->willReturn($stmt);
+
+ $resultado = $this->userController->getUserById(999);
+
+ $this->assertFalse($resultado);
+ }
+
+ public function test_getUserById_con_error_db()
+ {
+ $stmt = $this->createMock(\PDOStatement::class);
+ $stmt->method('execute')->willThrowException(new \PDOException('Error de conexión'));
+
+ $this->mockPDO->method('prepare')->willReturn($stmt);
+
+ $resultado = $this->userController->getUserById(1);
+
+ $this->assertNull($resultado);
+ }
+
+ /** @test */
+ public function login_guarda_id_en_sesion(): void
+ {
+ $mockStmt = $this->createMock(PDOStatement::class);
+ $mockStmt->method('fetch')->willReturn([
+ 'id' => 42,
+ 'password' => password_hash('test123', PASSWORD_BCRYPT),
+ 'name' => 'Test',
+ 'user_type' => 'user'
+ ]);
+
+ $this->mockPDO->method('prepare')->willReturn($mockStmt);
+
+ $this->userController->loginUser('test@test.com', 'test123');
+
+ $this->assertEquals(42, $_SESSION['user_id']);
+ }
+
+ /** @test */
+ public function login_guarda_nombre_en_sesion(): void
+ {
+ $mockStmt = $this->createMock(PDOStatement::class);
+ $mockStmt->method('fetch')->willReturn([
+ 'id' => 1,
+ 'name' => 'Pedro Test',
+ 'password' => password_hash('test123', PASSWORD_BCRYPT),
+ 'user_type' => 'user'
+ ]);
+
+ $this->mockPDO->method('prepare')->willReturn($mockStmt);
+
+ $this->userController->loginUser('test@test.com', 'test123');
+
+ $this->assertEquals('Pedro Test', $_SESSION['user_name']);
+ }
+
+ /** @test */
+ public function login_guarda_tipo_usuario_en_sesion(): void
+ {
+ $mockStmt = $this->createMock(PDOStatement::class);
+ $mockStmt->method('fetch')->willReturn([
+ 'id' => 1,
+ 'name' => 'Test',
+ 'password' => password_hash('test123', PASSWORD_BCRYPT),
+ 'user_type' => 'editor'
+ ]);
+
+ $this->mockPDO->method('prepare')->willReturn($mockStmt);
+
+ $this->userController->loginUser('test@test.com', 'test123');
+
+ $this->assertEquals('editor', $_SESSION['user_type']);
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Exceptions/DatabaseExceptionTest.php b/tests/Unit/Exceptions/DatabaseExceptionTest.php
new file mode 100644
index 00000000..a220c06a
--- /dev/null
+++ b/tests/Unit/Exceptions/DatabaseExceptionTest.php
@@ -0,0 +1,43 @@
+assertInstanceOf(DatabaseException::class, $exception);
+ $this->assertEquals($message, $exception->getMessage());
+ $this->assertEquals($code, $exception->getCode());
+ $this->assertSame($previous, $exception->getPrevious());
+ }
+
+ /** @test */
+ public function puede_crear_excepcion_sin_parametros(): void
+ {
+ $exception = new DatabaseException();
+
+ $this->assertInstanceOf(DatabaseException::class, $exception);
+ $this->assertEquals("", $exception->getMessage());
+ $this->assertEquals(0, $exception->getCode());
+ $this->assertNull($exception->getPrevious());
+ }
+
+ /** @test */
+ public function hereda_de_exception(): void
+ {
+ $exception = new DatabaseException();
+
+ $this->assertInstanceOf(\Exception::class, $exception);
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Models/MessageTest.php b/tests/Unit/Models/MessageTest.php
new file mode 100644
index 00000000..4449d2fa
--- /dev/null
+++ b/tests/Unit/Models/MessageTest.php
@@ -0,0 +1,134 @@
+message = new Message();
+ $this->conn = $this->createMock(PDO::class);
+ $this->pdoStatement = $this->createMock(PDOStatement::class);
+ }
+
+ /** @test */
+ public function puede_establecer_y_obtener_id(): void
+ {
+ $id = 1;
+ $this->message->setId($id);
+ $this->assertEquals($id, $this->message->getId());
+ }
+
+ /** @test */
+ public function puede_establecer_y_obtener_user_id(): void
+ {
+ $userId = 1;
+ $this->message->setUserId($userId);
+ $this->assertEquals($userId, $this->message->getUserId());
+ }
+
+ /** @test */
+ public function puede_establecer_y_obtener_name(): void
+ {
+ $name = "Juan Pérez";
+ $this->message->setName($name);
+ $this->assertEquals($name, $this->message->getName());
+ }
+
+ /** @test */
+ public function puede_establecer_y_obtener_email(): void
+ {
+ $email = "juan@example.com";
+ $this->message->setEmail($email);
+ $this->assertEquals($email, $this->message->getEmail());
+ }
+
+ /** @test */
+ public function puede_establecer_y_obtener_number(): void
+ {
+ $number = "123456789";
+ $this->message->setNumber($number);
+ $this->assertEquals($number, $this->message->getNumber());
+ }
+
+ /** @test */
+ public function puede_establecer_y_obtener_message(): void
+ {
+ $message = "Este es un mensaje de prueba";
+ $this->message->setMessage($message);
+ $this->assertEquals($message, $this->message->getMessage());
+ }
+
+ /** @test */
+ public function puede_guardar_mensaje(): void
+ {
+ $this->message->setUserId(1);
+ $this->message->setName("Juan");
+ $this->message->setEmail("juan@example.com");
+ $this->message->setNumber("123456789");
+ $this->message->setMessage("Mensaje de prueba");
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute')
+ ->willReturn(true);
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $result = $this->message->save($this->conn);
+ $this->assertTrue($result);
+ }
+
+ /** @test */
+ public function maneja_error_al_guardar(): void
+ {
+ $this->message->setUserId(1);
+ $this->message->setName("Juan");
+ $this->message->setEmail("juan@example.com");
+ $this->message->setNumber("123456789");
+ $this->message->setMessage("Mensaje de prueba");
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute')
+ ->willReturn(false);
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $result = $this->message->save($this->conn);
+ $this->assertFalse($result);
+ }
+
+ /** @test */
+ public function maneja_error_al_verificar_existencia(): void
+ {
+ $this->message->setUserId(1);
+ $this->message->setMessage("Mensaje de prueba");
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute')
+ ->willThrowException(new \PDOException("Error de prueba"));
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ try {
+ $result = $this->message->exists($this->conn);
+ $this->assertFalse($result);
+ } catch (\PDOException $e) {
+ $this->assertEquals("Error de prueba", $e->getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Models/UserTest.php b/tests/Unit/Models/UserTest.php
new file mode 100644
index 00000000..16cee5f8
--- /dev/null
+++ b/tests/Unit/Models/UserTest.php
@@ -0,0 +1,93 @@
+user = new User();
+ $this->conn = $this->createMock(PDO::class);
+ $this->pdoStatement = $this->createMock(PDOStatement::class);
+ }
+
+ /** @test */
+ public function verifica_constructor_inicializa_user_type(): void
+ {
+ $user = new User();
+ $this->assertEquals('user', $user->getUserType());
+ }
+
+ /** @test */
+ public function verifica_password_es_hasheado(): void
+ {
+ $password = "123456";
+ $this->user->setPassword($password);
+
+ $reflection = new \ReflectionClass($this->user);
+ $property = $reflection->getProperty('password');
+ $property->setAccessible(true);
+ $hashedPassword = $property->getValue($this->user);
+
+ $this->assertEquals(60, strlen($hashedPassword));
+ $this->assertTrue(password_verify($password, $hashedPassword));
+ }
+
+ /** @test */
+ public function verifica_exists_retorna_true_cuando_existe(): void
+ {
+ $this->user->setId(1);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute')
+ ->with([$this->user->getId()])
+ ->willReturn(true);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('rowCount')
+ ->willReturn(1);
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->assertTrue($this->user->exists($this->conn));
+ }
+
+ /** @test */
+ public function verifica_exists_retorna_false_cuando_no_existe(): void
+ {
+ $this->user->setId(999);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('execute')
+ ->with([$this->user->getId()])
+ ->willReturn(true);
+
+ $this->pdoStatement->expects($this->once())
+ ->method('rowCount')
+ ->willReturn(0);
+
+ $this->conn->expects($this->once())
+ ->method('prepare')
+ ->willReturn($this->pdoStatement);
+
+ $this->assertFalse($this->user->exists($this->conn));
+ }
+
+ /** @test */
+ public function verifica_setId_retorna_instancia(): void
+ {
+ $result = $this->user->setId(1);
+ $this->assertInstanceOf(User::class, $result);
+ $this->assertEquals(1, $this->user->getId());
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Views/ShopTest.php b/tests/Unit/Views/ShopTest.php
new file mode 100644
index 00000000..c1153c9b
--- /dev/null
+++ b/tests/Unit/Views/ShopTest.php
@@ -0,0 +1,87 @@
+db = $this->createMock(Database::class);
+ $this->productController = $this->createMock(ProductController::class);
+ }
+
+ public function test_puede_obtener_productos(): void
+ {
+ $productos_mock = [
+ new class {
+ public function getName() { return 'Producto Test'; }
+ public function getPrice() { return '100'; }
+ public function getImage() { return 'test.jpg'; }
+ }
+ ];
+
+ $this->productController
+ ->expects($this->once())
+ ->method('getAllProducts')
+ ->willReturn($productos_mock);
+
+ $products = $this->productController->getAllProducts();
+
+ $this->assertNotEmpty($products);
+ $this->assertEquals('Producto Test', $products[0]->getName());
+ $this->assertEquals('100', $products[0]->getPrice());
+ $this->assertEquals('test.jpg', $products[0]->getImage());
+ }
+
+ public function test_puede_anadir_al_carrito(): void
+ {
+ // Simular POST
+ $_POST['add_to_cart'] = true;
+ $_POST['product_name'] = 'Producto Test';
+ $_POST['product_price'] = '100';
+ $_POST['product_quantity'] = '1';
+ $_POST['product_image'] = 'test.jpg';
+
+ $this->productController
+ ->expects($this->once())
+ ->method('addToCart')
+ ->with(
+ $this->equalTo(1),
+ $this->equalTo($_POST)
+ )
+ ->willReturn(['message' => 'Producto añadido al carrito exitosamente']);
+
+ if(isset($_POST['add_to_cart'])) {
+ $result = $this->productController->addToCart(1, $_POST);
+ $message[] = $result['message'];
+ }
+
+ $this->assertArrayHasKey(0, $message);
+ $this->assertEquals('Producto añadido al carrito exitosamente', $message[0]);
+ }
+
+ public function test_muestra_mensaje_cuando_no_hay_productos(): void
+ {
+ $this->productController
+ ->method('getAllProducts')
+ ->willReturn([]);
+
+ $products = $this->productController->getAllProducts();
+
+ $this->assertEmpty($products);
+ }
+
+ protected function tearDown(): void
+ {
+ $_POST = array();
+ parent::tearDown();
+ }
+}
\ No newline at end of file
diff --git a/uploaded_img/test-image.jpg b/uploaded_img/test-image.jpg
new file mode 100644
index 00000000..1b8cbf4d
Binary files /dev/null and b/uploaded_img/test-image.jpg differ