From a4cce9eb23cb48f65bd3fb0c9b3be7ca88abf9ef Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 9 Jun 2026 01:09:27 -0500 Subject: [PATCH 1/8] Fix report sections, category create/rename, and docx rendering bugs - Show colored report section label per vuln in the vuln list, with live updates on edit/broadcast and a hash-based section color - Persist reportSection in the update broadcast so the left table stays in sync - API: create category by name when ID is missing, and rename when changed - DocxUtils: match getImage links for any assessment id, and fix ${end-section} tag lookup for named sections - Quiet pac4j logging to ERROR and extend CMS image reload timeout --- WebContent/WEB-INF/jsp/assessment/AddVuln.jsp | 21 +++++- WebContent/dist/js/cms.js | 2 +- WebContent/dist/js/vulnview.js | 2 +- WebContent/src/assessment/vulnview.js | 67 ++++++++++++++++++- WebContent/src/cms/cms.js | 2 +- .../actions/assessment/AddVulnerability.java | 3 +- src/com/fuse/api/vulnerabilities.java | 21 +++++- src/com/fuse/reporting/DocxUtils.java | 8 ++- src/logback.xml | 2 +- 9 files changed, 113 insertions(+), 15 deletions(-) diff --git a/WebContent/WEB-INF/jsp/assessment/AddVuln.jsp b/WebContent/WEB-INF/jsp/assessment/AddVuln.jsp index 458916dc..e54ab510 100644 --- a/WebContent/WEB-INF/jsp/assessment/AddVuln.jsp +++ b/WebContent/WEB-INF/jsp/assessment/AddVuln.jsp @@ -177,6 +177,18 @@ td:first-child { #details { background-color: white } + +.hideSection { + display:none; +} +.sectionName::after { + content: "\A"; + white-space: pre; +} +.sectionName { + border: solid 1px; + padding: 0px 4px 0px 4px; +}
@@ -488,9 +500,12 @@ td:first-child {

+ value="name" />
+
+
+ hideSection"> + " disabled>{var e={434:(e,t,n)=>{"use strict";n.d(t,{A:()=>s});var i=n(1601),r=n.n(i),o=n(6314),a=n.n(o)()(r());a.push([e.id,'/*!\n * icheck-bootstrap v3.0.1 (https://github.com/bantikyan/icheck-bootstrap)\n * Copyright 2018 Hovhannes Bantikyan.\n * Licensed under MIT (https://github.com/bantikyan/icheck-bootstrap/blob/master/LICENSE)\n */\n\n [class*="icheck-"] {\n min-height: 22px;\n margin-top: 6px !important;\n margin-bottom: 6px !important;\n padding-left: 0px;\n}\n\n.icheck-inline {\n display: inline-block;\n}\n\n .icheck-inline + .icheck-inline {\n margin-left: .75rem;\n margin-top: 6px;\n }\n\n[class*="icheck-"] > label {\n padding-left: 29px !important;\n min-height: 22px;\n line-height: 22px;\n display: inline-block;\n position: relative;\n vertical-align: top;\n margin-bottom: 0;\n font-weight: normal;\n cursor: pointer;\n}\n\n[class*="icheck-"] > input:first-child {\n position: absolute !important;\n opacity: 0;\n margin: 0;\n}\n\n [class*="icheck-"] > input:first-child:disabled {\n cursor: default;\n }\n\n [class*="icheck-"] > input:first-child + label::before,\n [class*="icheck-"] > input:first-child + input[type="hidden"] + label::before {\n content: "";\n display: inline-block;\n position: absolute;\n width: 22px;\n height: 22px;\n border: 1px solid #D3CFC8;\n border-radius: 0px;\n margin-left: -29px;\n }\n\n [class*="icheck-"] > input:first-child:checked + label::after,\n [class*="icheck-"] > input:first-child:checked + input[type="hidden"] + label::after {\n content: "";\n display: inline-block;\n position: absolute;\n top: 0;\n left: 0;\n width: 7px;\n height: 10px;\n border: solid 2px #fff;\n border-left: none;\n border-top: none;\n transform: translate(7.75px, 4.5px) rotate(45deg);\n -ms-transform: translate(7.75px, 4.5px) rotate(45deg);\n }\n\n[class*="icheck-"] > input[type="radio"]:first-child + label::before,\n[class*="icheck-"] > input[type="radio"]:first-child + input[type="hidden"] + label::before {\n border-radius: 50%;\n}\n\n[class*="icheck-"] > input:first-child:not(:checked):not(:disabled):hover + label::before,\n[class*="icheck-"] > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-width: 2px;\n}\n\n[class*="icheck-"] > input:first-child:disabled + label,\n[class*="icheck-"] > input:first-child:disabled + input[type="hidden"] + label,\n[class*="icheck-"] > input:first-child:disabled + label::before,\n[class*="icheck-"] > input:first-child:disabled + input[type="hidden"] + label::before {\n pointer-events: none;\n cursor: default;\n filter: alpha(opacity=65);\n -webkit-box-shadow: none;\n box-shadow: none;\n opacity: .65;\n}\n\n.icheck-default > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-default > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #adadad;\n}\n\n.icheck-default > input:first-child:checked + label::before,\n.icheck-default > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n\n.icheck-default > input:first-child:checked + label::after,\n.icheck-default > input:first-child:checked + input[type="hidden"] + label::after {\n border-bottom-color: #333;\n border-right-color: #333;\n}\n\n.icheck-primary > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-primary > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #2e6da4;\n}\n\n.icheck-primary > input:first-child:checked + label::before,\n.icheck-primary > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n\n.icheck-success > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-success > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #4cae4c;\n}\n\n.icheck-success > input:first-child:checked + label::before,\n.icheck-success > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n\n.icheck-info > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-info > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #46b8da;\n}\n\n.icheck-info > input:first-child:checked + label::before,\n.icheck-info > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n\n.icheck-warning > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-warning > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #eea236;\n}\n\n.icheck-warning > input:first-child:checked + label::before,\n.icheck-warning > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n\n.icheck-danger > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-danger > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #d43f3a;\n}\n\n.icheck-danger > input:first-child:checked + label::before,\n.icheck-danger > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n\n.icheck-turquoise > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-turquoise > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #1abc9c;\n}\n\n.icheck-turquoise > input:first-child:checked + label::before,\n.icheck-turquoise > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #1abc9c;\n border-color: #1abc9c;\n}\n\n.icheck-emerland > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-emerland > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #2ecc71;\n}\n\n.icheck-emerland > input:first-child:checked + label::before,\n.icheck-emerland > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #2ecc71;\n border-color: #2ecc71;\n}\n\n.icheck-peterriver > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-peterriver > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #3498db;\n}\n\n.icheck-peterriver > input:first-child:checked + label::before,\n.icheck-peterriver > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #3498db;\n border-color: #3498db;\n}\n\n.icheck-amethyst > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-amethyst > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #9b59b6;\n}\n\n.icheck-amethyst > input:first-child:checked + label::before,\n.icheck-amethyst > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #9b59b6;\n border-color: #9b59b6;\n}\n\n.icheck-wetasphalt > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-wetasphalt > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #34495e;\n}\n\n.icheck-wetasphalt > input:first-child:checked + label::before,\n.icheck-wetasphalt > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #34495e;\n border-color: #34495e;\n}\n\n.icheck-greensea > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-greensea > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #16a085;\n}\n\n.icheck-greensea > input:first-child:checked + label::before,\n.icheck-greensea > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #16a085;\n border-color: #16a085;\n}\n\n.icheck-nephritis > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-nephritis > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #27ae60;\n}\n\n.icheck-nephritis > input:first-child:checked + label::before,\n.icheck-nephritis > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #27ae60;\n border-color: #27ae60;\n}\n\n.icheck-belizehole > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-belizehole > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #2980b9;\n}\n\n.icheck-belizehole > input:first-child:checked + label::before,\n.icheck-belizehole > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #2980b9;\n border-color: #2980b9;\n}\n\n.icheck-wisteria > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-wisteria > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #8e44ad;\n}\n\n.icheck-wisteria > input:first-child:checked + label::before,\n.icheck-wisteria > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #8e44ad;\n border-color: #8e44ad;\n}\n\n.icheck-midnightblue > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-midnightblue > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #2c3e50;\n}\n\n.icheck-midnightblue > input:first-child:checked + label::before,\n.icheck-midnightblue > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #2c3e50;\n border-color: #2c3e50;\n}\n\n.icheck-sunflower > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-sunflower > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #f1c40f;\n}\n\n.icheck-sunflower > input:first-child:checked + label::before,\n.icheck-sunflower > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #f1c40f;\n border-color: #f1c40f;\n}\n\n.icheck-carrot > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-carrot > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #e67e22;\n}\n\n.icheck-carrot > input:first-child:checked + label::before,\n.icheck-carrot > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #e67e22;\n border-color: #e67e22;\n}\n\n.icheck-alizarin > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-alizarin > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #e74c3c;\n}\n\n.icheck-alizarin > input:first-child:checked + label::before,\n.icheck-alizarin > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #e74c3c;\n border-color: #e74c3c;\n}\n\n.icheck-clouds > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-clouds > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #ecf0f1;\n}\n\n.icheck-clouds > input:first-child:checked + label::before,\n.icheck-clouds > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #ecf0f1;\n border-color: #ecf0f1;\n}\n\n.icheck-clouds > input:first-child:checked + label::after,\n.icheck-clouds > input:first-child:checked + input[type="hidden"] + label::after {\n border-bottom-color: #95a5a6;\n border-right-color: #95a5a6;\n}\n\n.icheck-concrete > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-concrete > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #95a5a6;\n}\n\n.icheck-concrete > input:first-child:checked + label::before,\n.icheck-concrete > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #95a5a6;\n border-color: #95a5a6;\n}\n\n.icheck-orange > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-orange > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #f39c12;\n}\n\n.icheck-orange > input:first-child:checked + label::before,\n.icheck-orange > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #f39c12;\n border-color: #f39c12;\n}\n\n.icheck-pumpkin > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-pumpkin > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #d35400;\n}\n\n.icheck-pumpkin > input:first-child:checked + label::before,\n.icheck-pumpkin > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #d35400;\n border-color: #d35400;\n}\n\n.icheck-pomegranate > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-pomegranate > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #c0392b;\n}\n\n.icheck-pomegranate > input:first-child:checked + label::before,\n.icheck-pomegranate > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #c0392b;\n border-color: #c0392b;\n}\n\n.icheck-silver > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-silver > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #bdc3c7;\n}\n\n.icheck-silver > input:first-child:checked + label::before,\n.icheck-silver > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #bdc3c7;\n border-color: #bdc3c7;\n}\n\n.icheck-asbestos > input:first-child:not(:checked):not(:disabled):hover + label::before,\n.icheck-asbestos > input:first-child:not(:checked):not(:disabled):hover + input[type="hidden"] + label::before {\n border-color: #7f8c8d;\n}\n\n.icheck-asbestos > input:first-child:checked + label::before,\n.icheck-asbestos > input:first-child:checked + input[type="hidden"] + label::before {\n background-color: #7f8c8d;\n border-color: #7f8c8d;\n}',""]);const s=a},540:e=>{"use strict";e.exports=function(e){var t=document.createElement("style");return e.setAttributes(t,e.attributes),e.insert(t,e.options),t}},576:(e,t,n)=>{!function(e){"use strict";var t={autoSelfClosers:{area:!0,base:!0,br:!0,col:!0,command:!0,embed:!0,frame:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0,menuitem:!0},implicitlyClosed:{dd:!0,li:!0,optgroup:!0,option:!0,p:!0,rp:!0,rt:!0,tbody:!0,td:!0,tfoot:!0,th:!0,tr:!0},contextGrabbers:{dd:{dd:!0,dt:!0},dt:{dd:!0,dt:!0},li:{li:!0},option:{option:!0,optgroup:!0},optgroup:{optgroup:!0},p:{address:!0,article:!0,aside:!0,blockquote:!0,dir:!0,div:!0,dl:!0,fieldset:!0,footer:!0,form:!0,h1:!0,h2:!0,h3:!0,h4:!0,h5:!0,h6:!0,header:!0,hgroup:!0,hr:!0,menu:!0,nav:!0,ol:!0,p:!0,pre:!0,section:!0,table:!0,ul:!0},rp:{rp:!0,rt:!0},rt:{rp:!0,rt:!0},tbody:{tbody:!0,tfoot:!0},td:{td:!0,th:!0},tfoot:{tbody:!0},th:{td:!0,th:!0},thead:{tbody:!0,tfoot:!0},tr:{tr:!0}},doNotIndent:{pre:!0},allowUnquoted:!0,allowMissing:!0,caseFold:!0},n={autoSelfClosers:{},implicitlyClosed:{},contextGrabbers:{},doNotIndent:{},allowUnquoted:!1,allowMissing:!1,allowMissingTagName:!1,caseFold:!1};e.defineMode("xml",function(i,r){var o,a,s=i.indentUnit,l={},c=r.htmlMode?t:n;for(var u in c)l[u]=c[u];for(var u in r)l[u]=r[u];function d(e,t){function n(n){return t.tokenize=n,n(e,t)}var i=e.next();return"<"==i?e.eat("!")?e.eat("[")?e.match("CDATA[")?n(h("atom","]]>")):null:e.match("--")?n(h("comment","--\x3e")):e.match("DOCTYPE",!0,!0)?(e.eatWhile(/[\w\._\-]/),n(g(1))):null:e.eat("?")?(e.eatWhile(/[\w\._\-]/),t.tokenize=h("meta","?>"),"meta"):(o=e.eat("/")?"closeTag":"openTag",t.tokenize=f,"tag bracket"):"&"==i?(e.eat("#")?e.eat("x")?e.eatWhile(/[a-fA-F\d]/)&&e.eat(";"):e.eatWhile(/[\d]/)&&e.eat(";"):e.eatWhile(/[\w\.\-:]/)&&e.eat(";"))?"atom":"error":(e.eatWhile(/[^&<]/),null)}function f(e,t){var n=e.next();if(">"==n||"/"==n&&e.eat(">"))return t.tokenize=d,o=">"==n?"endTag":"selfcloseTag","tag bracket";if("="==n)return o="equals",null;if("<"==n){t.tokenize=d,t.state=w,t.tagName=t.tagStart=null;var i=t.tokenize(e,t);return i?i+" tag error":"tag error"}return/[\'\"]/.test(n)?(t.tokenize=p(n),t.stringStartCol=e.column(),t.tokenize(e,t)):(e.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/),"word")}function p(e){var t=function(t,n){for(;!t.eol();)if(t.next()==e){n.tokenize=f;break}return"string"};return t.isInAttribute=!0,t}function h(e,t){return function(n,i){for(;!n.eol();){if(n.match(t)){i.tokenize=d;break}n.next()}return e}}function g(e){return function(t,n){for(var i;null!=(i=t.next());){if("<"==i)return n.tokenize=g(e+1),n.tokenize(t,n);if(">"==i){if(1==e){n.tokenize=d;break}return n.tokenize=g(e-1),n.tokenize(t,n)}}return"meta"}}function m(e){return e&&e.toLowerCase()}function v(e,t,n){this.prev=e.context,this.tagName=t||"",this.indent=e.indented,this.startOfLine=n,(l.doNotIndent.hasOwnProperty(t)||e.context&&e.context.noIndent)&&(this.noIndent=!0)}function b(e){e.context&&(e.context=e.context.prev)}function y(e,t){for(var n;;){if(!e.context)return;if(n=e.context.tagName,!l.contextGrabbers.hasOwnProperty(m(n))||!l.contextGrabbers[m(n)].hasOwnProperty(m(t)))return;b(e)}}function w(e,t,n){return"openTag"==e?(n.tagStart=t.column(),x):"closeTag"==e?C:w}function x(e,t,n){return"word"==e?(n.tagName=t.current(),a="tag",S):l.allowMissingTagName&&"endTag"==e?(a="tag bracket",S(e,t,n)):(a="error",x)}function C(e,t,n){if("word"==e){var i=t.current();return n.context&&n.context.tagName!=i&&l.implicitlyClosed.hasOwnProperty(m(n.context.tagName))&&b(n),n.context&&n.context.tagName==i||!1===l.matchClosing?(a="tag",k):(a="tag error",_)}return l.allowMissingTagName&&"endTag"==e?(a="tag bracket",k(e,t,n)):(a="error",_)}function k(e,t,n){return"endTag"!=e?(a="error",k):(b(n),w)}function _(e,t,n){return a="error",k(e,t,n)}function S(e,t,n){if("word"==e)return a="attribute",T;if("endTag"==e||"selfcloseTag"==e){var i=n.tagName,r=n.tagStart;return n.tagName=n.tagStart=null,"selfcloseTag"==e||l.autoSelfClosers.hasOwnProperty(m(i))?y(n,i):(y(n,i),n.context=new v(n,i,r==n.indented)),w}return a="error",S}function T(e,t,n){return"equals"==e?D:(l.allowMissing||(a="error"),S(e,t,n))}function D(e,t,n){return"string"==e?A:"word"==e&&l.allowUnquoted?(a="string",S):(a="error",S(e,t,n))}function A(e,t,n){return"string"==e?A:S(e,t,n)}return d.isInText=!0,{startState:function(e){var t={tokenize:d,state:w,indented:e||0,tagName:null,tagStart:null,context:null};return null!=e&&(t.baseIndent=e),t},token:function(e,t){if(!t.tagName&&e.sol()&&(t.indented=e.indentation()),e.eatSpace())return null;o=null;var n=t.tokenize(e,t);return(n||o)&&"comment"!=n&&(a=null,t.state=t.state(o||n,e,t),a&&(n="error"==a?n+" error":a)),n},indent:function(t,n,i){var r=t.context;if(t.tokenize.isInAttribute)return t.tagStart==t.indented?t.stringStartCol+1:t.indented+s;if(r&&r.noIndent)return e.Pass;if(t.tokenize!=f&&t.tokenize!=d)return i?i.match(/^(\s*)/)[0].length:0;if(t.tagName)return!1!==l.multilineTagIndentPastTag?t.tagStart+t.tagName.length+2:t.tagStart+s*(l.multilineTagIndentFactor||1);if(l.alignCDATA&&/$/,blockCommentStart:"\x3c!--",blockCommentEnd:"--\x3e",configuration:l.htmlMode?"html":"xml",helperType:l.htmlMode?"html":"xml",skipAttribute:function(e){e.state==D&&(e.state=S)},xmlCurrentTag:function(e){return e.tagName?{name:e.tagName,close:"closeTag"==e.type}:null},xmlCurrentContext:function(e){for(var t=[],n=e.context;n;n=n.prev)t.push(n.tagName);return t.reverse()}}}),e.defineMIME("text/xml","xml"),e.defineMIME("application/xml","xml"),e.mimeModes.hasOwnProperty("text/html")||e.defineMIME("text/html",{name:"xml",htmlMode:!0})}(n(5237))},1113:e=>{"use strict";e.exports=function(e,t){if(t.styleSheet)t.styleSheet.cssText=e;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(e))}}},1601:e=>{"use strict";e.exports=function(e){return e[1]}},2003:(e,t,n)=>{var i,r,o;!function(){"use strict";r=[n(4692)],i=function(e){var t,n;e.fn.fileinputLocales={},e.fn.fileinputThemes={},String.prototype.setTokens=function(e){var t,n,i=this.toString();for(t in e)e.hasOwnProperty(t)&&(n=new RegExp("{"+t+"}","g"),i=i.replace(n,e[t]));return i},t={FRAMES:".kv-preview-thumb",SORT_CSS:"file-sortable",OBJECT_PARAMS:'\n\n\n\n\n\n',DEFAULT_PREVIEW:'
\n{previewFileIcon}\n
',MODAL_ID:"kvFileinputModal",MODAL_EVENTS:["show","shown","hide","hidden","loaded"],objUrl:window.URL||window.webkitURL,compare:function(e,t,n){return void 0!==e&&(n?e===t:e.match(t))},isIE:function(e){if("Microsoft Internet Explorer"!==navigator.appName)return!1;if(10===e)return new RegExp("msie\\s"+e,"i").test(navigator.userAgent);var t,n=document.createElement("div");return n.innerHTML="\x3c!--[if IE "+e+"]> =0?atob(e.split(",")[1]):decodeURIComponent(e.split(",")[1]),i=new ArrayBuffer(n.length),r=new Uint8Array(i),o=0;o/g,">").replace(/"/g,""").replace(/'/g,"'")},replaceTags:function(t,n){var i=t;return n?(e.each(n,function(e,t){"function"==typeof t&&(t=t()),i=i.split(e).join(t)}),i):i},cleanMemory:function(e){var n=e.is("img")?e.attr("src"):e.find("source").attr("src");t.objUrl.revokeObjectURL(n)},findFileName:function(e){var t=e.lastIndexOf("/");return-1===t&&(t=e.lastIndexOf("\\")),e.split(e.substring(t,t+1)).pop()},checkFullScreen:function(){return document.fullscreenElement||document.mozFullScreenElement||document.webkitFullscreenElement||document.msFullscreenElement},toggleFullScreen:function(e){var n=document,i=n.documentElement;i&&e&&!t.checkFullScreen()?i.requestFullscreen?i.requestFullscreen():i.msRequestFullscreen?i.msRequestFullscreen():i.mozRequestFullScreen?i.mozRequestFullScreen():i.webkitRequestFullscreen&&i.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT):n.exitFullscreen?n.exitFullscreen():n.msExitFullscreen?n.msExitFullscreen():n.mozCancelFullScreen?n.mozCancelFullScreen():n.webkitExitFullscreen&&n.webkitExitFullscreen()},moveArray:function(e,t,n){if(n>=e.length)for(var i=n-e.length;1+i--;)e.push(void 0);return e.splice(n,0,e.splice(t,1)[0]),e},cleanZoomCache:function(e){var t=e.closest(".kv-zoom-cache-theme");t.length||(t=e.closest(".kv-zoom-cache")),t.remove()},setOrientation:function(e,t){var n,i,r=new DataView(e),o=0,a=1;if(65496!==r.getUint16(o)||e.length<2)t&&t();else{for(o+=2,n=r.byteLength;o
\n
\n {caption}\n
\n {browse}\n
\n
',i='{preview}\n
\n
\n{remove}\n{cancel}\n{upload}\n{browse}\n',r='
\n {close}
\n
\n
\n
\n
\n
\n
',a='\n',o='',s='
\n
\n
\n',l='',c='{icon} {label}',u='
{icon} {label}
',d='',f='\n',p='
\n
\n {status}\n
\n
',h=" ({sizeText})",g='',m='
\n \n
\n{drag}\n
',v='\n',b='',y='{downloadIcon}',w='',x='{dragIcon}',C='
{indicator}
',_=(k='
\n',S=k+' title="{caption}">
\n',T="
{footer}\n
\n",D='
{data}
\n',A='{caption}\n',E='\n',L='\n",I='\n",$='\n'+t.OBJECT_PARAMS+" "+t.DEFAULT_PREVIEW+"\n\n",P='\n\n'+t.OBJECT_PARAMS+" "+t.DEFAULT_PREVIEW+"\n\n",M='\n',F='
\n'+t.DEFAULT_PREVIEW+"\n
\n",j='',O={width:"100%",height:"100%","min-height":"480px"},N.defaults={layoutTemplates:{main1:n,main2:i,preview:r,close:a,fileIcon:o,caption:s,modalMain:d,modal:f,progress:p,size:h,footer:g,indicator:C,actions:m,actionDelete:v,actionUpload:b,actionDownload:y,actionZoom:w,actionDrag:x,btnDefault:l,btnLink:c,btnBrowse:u,zoomCache:j},previewMarkupTags:{tagBefore1:_,tagBefore2:S,tagAfter:T},previewContentTemplates:{generic:"{content}\n",html:D,image:A,text:E,video:L,audio:I,flash:$,object:P,pdf:M,other:F},allowedPreviewTypes:["image","html","text","video","audio","flash","pdf","object"],previewTemplates:{},previewSettings:{image:{width:"auto",height:"auto","max-width":"100%","max-height":"100%"},html:{width:"213px",height:"160px"},text:{width:"213px",height:"160px"},video:{width:"213px",height:"160px"},audio:{width:"100%",height:"30px"},flash:{width:"213px",height:"160px"},object:{width:"213px",height:"160px"},pdf:{width:"213px",height:"160px"},other:{width:"213px",height:"160px"}},previewSettingsSmall:{image:{width:"auto",height:"auto","max-width":"100%","max-height":"100%"},html:{width:"100%",height:"160px"},text:{width:"100%",height:"160px"},video:{width:"100%",height:"auto"},audio:{width:"100%",height:"30px"},flash:{width:"100%",height:"auto"},object:{width:"100%",height:"auto"},pdf:{width:"100%",height:"160px"},other:{width:"100%",height:"160px"}},previewZoomSettings:{image:{width:"auto",height:"auto","max-width":"100%","max-height":"100%"},html:O,text:O,video:{width:"auto",height:"100%","max-width":"100%"},audio:{width:"100%",height:"30px"},flash:{width:"auto",height:"480px"},object:{width:"auto",height:"100%","max-width":"100%","min-height":"480px"},pdf:O,other:{width:"auto",height:"100%","min-height":"480px"}},fileTypeSettings:{image:function(e,n){return t.compare(e,"image.*")||t.compare(n,/\.(gif|png|jpe?g)$/i)},html:function(e,n){return t.compare(e,"text/html")||t.compare(n,/\.(htm|html)$/i)},text:function(e,n){return t.compare(e,"text.*")||t.compare(n,/\.(xml|javascript)$/i)||t.compare(n,/\.(txt|md|csv|nfo|ini|json|php|js|css)$/i)},video:function(e,n){return t.compare(e,"video.*")&&(t.compare(e,/(ogg|mp4|mp?g|mov|webm|3gp)$/i)||t.compare(n,/\.(og?|mp4|webm|mp?g|mov|3gp)$/i))},audio:function(e,n){return t.compare(e,"audio.*")&&(t.compare(n,/(ogg|mp3|mp?g|wav)$/i)||t.compare(n,/\.(og?|mp3|mp?g|wav)$/i))},flash:function(e,n){return t.compare(e,"application/x-shockwave-flash",!0)||t.compare(n,/\.(swf)$/i)},pdf:function(e,n){return t.compare(e,"application/pdf",!0)||t.compare(n,/\.(pdf)$/i)},object:function(){return!0},other:function(){return!0}},fileActionSettings:{showRemove:!0,showUpload:!0,showDownload:!0,showZoom:!0,showDrag:!0,removeIcon:'',removeClass:"btn btn-sm btn-default btn-outline-secondary",removeErrorClass:"btn btn-sm btn-danger",removeTitle:"Remove file",uploadIcon:'',uploadClass:"btn btn-sm btn-default btn-outline-secondary",uploadTitle:"Upload file",uploadRetryIcon:'',uploadRetryTitle:"Retry upload",downloadIcon:'',downloadClass:"btn btn-sm btn-default btn-outline-secondary",downloadTitle:"Download file",zoomIcon:'',zoomClass:"btn btn-sm btn-default btn-outline-secondary",zoomTitle:"View Details",dragIcon:'',dragClass:"text-info",dragTitle:"Move / Rearrange",dragSettings:{},indicatorNew:'',indicatorSuccess:'',indicatorError:'',indicatorLoading:'',indicatorNewTitle:"Not uploaded yet",indicatorSuccessTitle:"Uploaded",indicatorErrorTitle:"Upload Error",indicatorLoadingTitle:"Uploading ..."}},e.each(N.defaults,function(t,n){"allowedPreviewTypes"!==t?N[t]=e.extend(!0,{},n,N[t]):void 0===N.allowedPreviewTypes&&(N.allowedPreviewTypes=n)}),N._initPreviewTemplates()},_initPreviewTemplates:function(){var n,i=this,r=i.defaults,o=i.previewMarkupTags,a=o.tagAfter;e.each(r.previewContentTemplates,function(e,r){t.isEmpty(i.previewTemplates[e])&&(n=o.tagBefore2,"generic"!==e&&"image"!==e&&"html"!==e&&"text"!==e||(n=o.tagBefore1),i.previewTemplates[e]=n+r+a)})},_initPreviewCache:function(){var n=this;n.previewCache={data:{},init:function(){var e=n.initialPreview;e.length>0&&!t.isArray(e)&&(e=e.split(n.initialPreviewDelimiter)),n.previewCache.data={content:e,config:n.initialPreviewConfig,tags:n.initialPreviewThumbTags}},count:function(){return n.previewCache.data&&n.previewCache.data.content?n.previewCache.data.content.length:0},get:function(i,r){var o,a,s,l,c,u,d,f="init_"+i,p=n.previewCache.data,h=p.config[i],g=p.content[i],m=n.previewInitId+"-"+f,v=t.ifSet("previewAsData",h,n.initialPreviewAsData),b=function(e,i,r,o,a,s,l,c,u){return c=" file-preview-initial "+t.SORT_CSS+(c?" "+c:""),n._generatePreviewTemplate(e,i,r,o,a,!1,null,c,s,l,u)};return g?(r=void 0===r||r,s=t.ifSet("type",h,n.initialPreviewFileType||"generic"),c=t.ifSet("filename",h,t.ifSet("caption",h)),u=t.ifSet("filetype",h,s),l=n.previewCache.footer(i,r,h&&h.size||null),d=t.ifSet("frameClass",h),o=v?b(s,g,c,u,m,l,f,d):b("generic",g,c,u,m,l,f,d,s).setTokens({content:p.content[i]}),p.tags.length&&p.tags[i]&&(o=t.replaceTags(o,p.tags[i])),t.isEmpty(h)||t.isEmpty(h.frameAttr)||((a=e(document.createElement("div")).html(o)).find(".file-preview-initial").attr(h.frameAttr),o=a.html(),a.remove()),o):""},add:function(e,i,r,o){var a,s=n.previewCache.data;return t.isArray(e)||(e=e.split(n.initialPreviewDelimiter)),o?(a=s.content.push(e)-1,s.config[a]=i,s.tags[a]=r):(a=e.length-1,s.content=e,s.config=i,s.tags=r),n.previewCache.data=s,a},set:function(e,i,r,o){var a,s=n.previewCache.data;if(e&&e.length&&(t.isArray(e)||(e=e.split(n.initialPreviewDelimiter)),e.filter(function(e){return null!==e}).length)){if(void 0===s.content&&(s.content=[]),void 0===s.config&&(s.config=[]),void 0===s.tags&&(s.tags=[]),o){for(a=0;a'+e+"":"
  • "+e+"
  • ";return 0===o.find("ul").length?r._addError("
      "+s+"
    "):o.find("ul").append(s),o.fadeIn(800),r._raise(a,[n,e]),r.$container.removeClass("file-input-new"),t.addCss(r.$container,"has-error"),!0},_showError:function(e,n,i){var r=this,o=r.$errorContainer,a=i||"fileerror";return(n=n||{}).reader=r.reader,r._addError(e),o.fadeIn(800),r._raise(a,[n,e]),r.isUploadable||r._clearFileInput(),r.$container.removeClass("file-input-new"),t.addCss(r.$container,"has-error"),r.$btnUpload.attr("disabled",!0),!0},_noFilesError:function(e){var n=this,i=n.minFileCount>1?n.filePlural:n.fileSingle,r=n.msgFilesTooLess.replace("{n}",n.minFileCount).replace("{files}",i),o=n.$errorContainer;n._addError(r),n.isError=!0,n._updateFileDetails(0),o.fadeIn(800),n._raise("fileerror",[e,r]),n._clearFileInput(),t.addCss(n.$container,"has-error")},_parseError:function(t,n,i,r){var o,a=this,s=e.trim(i+""),l=void 0!==n.responseJSON&&void 0!==n.responseJSON.error?n.responseJSON.error:n.responseText;return a.cancelling&&a.msgUploadAborted&&(s=a.msgUploadAborted),a.showAjaxErrorDetails&&l&&(o=(l=e.trim(l.replace(/\n\s*\n/g,"\n"))).length?"
    "+l+"
    ":"",s+=s?o:l),s||(s=a.msgAjaxError.replace("{operation}",t)),a.cancelling=!1,r?""+r+": "+s:s},_parseFileType:function(e){var n,i,r,o=this,a=o.allowedPreviewTypes||[];for(r=0;r-1&&(n=t.split(".").pop(),i.previewFileIconSettings&&(r=i.previewFileIconSettings[n]||i.previewFileIconSettings[n.toLowerCase()]||null),i.previewFileExtSettings&&e.each(i.previewFileExtSettings,function(e,t){i.previewFileIconSettings[e]&&t(n)&&(r=i.previewFileIconSettings[e])})),r},_parseFilePreviewIcon:function(e,t){var n=this,i=n._getPreviewIcon(t)||n.previewFileIcon,r=e;return r.indexOf("{previewFileIcon}")>-1&&(r=r.setTokens({previewFileIconClass:n.previewFileIconClass,previewFileIcon:i})),r},_raise:function(t,n){var i=this,r=e.Event(t);if(void 0!==n?i.$element.trigger(r,n):i.$element.trigger(r),r.isDefaultPrevented()||!1===r.result)return!1;switch(t){case"filebatchuploadcomplete":case"filebatchuploadsuccess":case"fileuploaded":case"fileclear":case"filecleared":case"filereset":case"fileerror":case"filefoldererror":case"fileuploaderror":case"filebatchuploaderror":case"filedeleteerror":case"filecustomerror":case"filesuccessremove":break;default:i.ajaxAborted||(i.ajaxAborted=r.result)}return!0},_listenFullScreen:function(e){var t,n,i=this,r=i.$modal;r&&r.length&&(t=r&&r.find(".btn-fullscreen"),n=r&&r.find(".btn-borderless"),t.length&&n.length&&(t.removeClass("active").attr("aria-pressed","false"),n.removeClass("active").attr("aria-pressed","false"),e?t.addClass("active").attr("aria-pressed","true"):n.addClass("active").attr("aria-pressed","true"),r.hasClass("file-zoom-fullscreen")||e?i._maximizeZoomDialog():n.removeClass("active").attr("aria-pressed","false")))},_listen:function(){var n,i=this,r=i.$element,o=i.$form,a=i.$container;i._handler(r,"change",e.proxy(i._change,i)),i.showBrowse&&i._handler(i.$btnFile,"click",e.proxy(i._browse,i)),i._handler(a.find(".fileinput-remove:not([disabled])"),"click",e.proxy(i.clear,i)),i._handler(a.find(".fileinput-cancel"),"click",e.proxy(i.cancel,i)),i._initDragDrop(),i._handler(o,"reset",e.proxy(i.clear,i)),i.isUploadable||i._handler(o,"submit",e.proxy(i._submitForm,i)),i._handler(i.$container.find(".fileinput-upload"),"click",e.proxy(i._uploadClick,i)),i._handler(e(window),"resize",function(){i._listenFullScreen(screen.width===window.innerWidth&&screen.height===window.innerHeight)}),n="webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange",i._handler(e(document),n,function(){i._listenFullScreen(t.checkFullScreen())}),i._autoFitContent(),i._initClickable()},_autoFitContent:function(){var t,n=this,i=(window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth)<400?n.previewSettingsSmall||n.defaults.previewSettingsSmall:n.previewSettings||n.defaults.previewSettings;e.each(i,function(e,i){t=".file-preview-frame .file-preview-"+e,n.$preview.find(t+".kv-preview-data,"+t+" .kv-preview-data").css(i)})},_initClickable:function(){var n,i=this;i.isClickable&&(n=i.isUploadable?i.$dropZone:i.$preview.find(".file-default-preview"),t.addCss(n,"clickable"),n.attr("tabindex",-1),i._handler(n,"click",function(t){var r=e(t.target);r.parents(".file-preview-thumbnails").length&&!r.parents(".file-default-preview").length||(i.$element.trigger("click"),n.blur())}))},_initDragDrop:function(){var t=this,n=t.$dropZone;t.isUploadable&&t.dropZoneEnabled&&t.showPreview&&(t._handler(n,"dragenter dragover",e.proxy(t._zoneDragEnter,t)),t._handler(n,"dragleave",e.proxy(t._zoneDragLeave,t)),t._handler(n,"drop",e.proxy(t._zoneDrop,t)),t._handler(e(document),"dragenter dragover drop",t._zoneDragDropInit))},_zoneDragDropInit:function(e){e.stopPropagation(),e.preventDefault()},_zoneDragEnter:function(n){var i=this,r=e.inArray("Files",n.originalEvent.dataTransfer.types)>-1;if(i._zoneDragDropInit(n),i.isDisabled||!r)return n.originalEvent.dataTransfer.effectAllowed="none",void(n.originalEvent.dataTransfer.dropEffect="none");t.addCss(i.$dropZone,"file-highlighted")},_zoneDragLeave:function(e){var t=this;t._zoneDragDropInit(e),t.isDisabled||t.$dropZone.removeClass("file-highlighted")},_zoneDrop:function(e){var n=this;e.preventDefault(),n.isDisabled||t.isEmpty(e.originalEvent.dataTransfer.files)||(n._change(e,"dragdrop"),n.$dropZone.removeClass("file-highlighted"))},_uploadClick:function(e){var n,i=this,r=i.$container.find(".fileinput-upload"),o=!r.hasClass("disabled")&&t.isEmpty(r.attr("disabled"));e&&e.isDefaultPrevented()||(i.isUploadable?(e.preventDefault(),o&&i.upload()):o&&"submit"!==r.attr("type")&&((n=r.closest("form")).length&&n.trigger("submit"),e.preventDefault()))},_submitForm:function(){var e=this;return e._isFileSelectionValid()&&!e._abort({})},_clearPreview:function(){var n=this,i=n.$preview;(n.showUploadedThumbs?n.getFrames(":not(.file-preview-success)"):n.getFrames()).each(function(){var n=e(this);n.remove(),t.cleanZoomCache(i.find("#zoom-"+n.attr("id")))}),n.getFrames().length&&n.showPreview||n._resetUpload(),n._validateDefaultPreview()},_initSortable:function(){var n,i=this,r=i.$preview,o="."+t.SORT_CSS;window.KvSortable&&0!==r.find(o).length&&(n={handle:".drag-handle-init",dataIdAttr:"data-preview-id",scroll:!1,draggable:o,onSort:function(n){var r,o=n.oldIndex,a=n.newIndex;i.initialPreview=t.moveArray(i.initialPreview,o,a),i.initialPreviewConfig=t.moveArray(i.initialPreviewConfig,o,a),i.previewCache.init();for(var s=0;s
    ',next:'',toggleheader:'',fullscreen:'',borderless:'',close:''},previewZoomButtonClasses:{prev:"btn btn-navigate",next:"btn btn-navigate",toggleheader:"btn btn-default btn-secondary btn-header-toggle",fullscreen:"btn btn-default btn-secondary",borderless:"btn btn-default btn-secondary",close:"btn btn-default btn-secondary"},preferIconicPreview:!1,preferIconicZoomPreview:!1,allowedPreviewTypes:void 0,allowedPreviewMimeTypes:null,allowedFileTypes:null,allowedFileExtensions:null,defaultPreviewContent:null,customLayoutTags:{},customPreviewTags:{},previewFileIcon:'',previewFileIconClass:"file-other-icon",previewFileIconSettings:{},previewFileExtSettings:{},buttonLabelClass:"hidden-xs",browseIcon:' ',browseClass:"btn btn-primary",removeIcon:'',removeClass:"btn btn-default btn-secondary",cancelIcon:'',cancelClass:"btn btn-default btn-secondary",uploadIcon:'',uploadClass:"btn btn-default btn-secondary",uploadUrl:null,uploadUrlThumb:null,uploadAsync:!0,uploadExtraData:{},zoomModalHeight:480,minImageWidth:null,minImageHeight:null,maxImageWidth:null,maxImageHeight:null,resizeImage:!1,resizePreference:"width",resizeQuality:.92,resizeDefaultImageType:"image/jpeg",resizeIfSizeMoreThan:0,minFileSize:0,maxFileSize:0,maxFilePreviewSize:25600,minFileCount:0,maxFileCount:0,validateInitialCount:!1,msgValidationErrorClass:"text-danger",msgValidationErrorIcon:' ',msgErrorClass:"file-error-message",progressThumbClass:"progress-bar bg-success progress-bar-success progress-bar-striped active",progressClass:"progress-bar bg-success progress-bar-success progress-bar-striped active",progressCompleteClass:"progress-bar bg-success progress-bar-success",progressErrorClass:"progress-bar bg-danger progress-bar-danger",progressUploadThreshold:99,previewFileType:"image",elCaptionContainer:null,elCaptionText:null,elPreviewContainer:null,elPreviewImage:null,elPreviewStatus:null,elErrorContainer:null,errorCloseButton:'',slugCallback:null,dropZoneEnabled:!0,dropZoneTitleClass:"file-drop-zone-title",fileActionSettings:{},otherActionButtons:"",textEncoding:"UTF-8",ajaxSettings:{},ajaxDeleteSettings:{},showAjaxErrorDetails:!0,mergeAjaxCallbacks:!1,mergeAjaxDeleteCallbacks:!1,retryErrorUploads:!0},e.fn.fileinputLocales.en={fileSingle:"file",filePlural:"files",browseLabel:"Browse …",removeLabel:"Remove",removeTitle:"Clear selected files",cancelLabel:"Cancel",cancelTitle:"Abort ongoing upload",uploadLabel:"Upload",uploadTitle:"Upload selected files",msgNo:"No",msgNoFilesSelected:"No files selected",msgCancelled:"Cancelled",msgZoomModalHeading:"Detailed Preview",msgFileRequired:"You must select a file to upload.",msgSizeTooSmall:'File "{name}" ({size} KB) is too small and must be larger than {minSize} KB.',msgSizeTooLarge:'File "{name}" ({size} KB) exceeds maximum allowed upload size of {maxSize} KB.',msgFilesTooLess:"You must select at least {n} {files} to upload.",msgFilesTooMany:"Number of files selected for upload ({n}) exceeds maximum allowed limit of {m}.",msgFileNotFound:'File "{name}" not found!',msgFileSecured:'Security restrictions prevent reading the file "{name}".',msgFileNotReadable:'File "{name}" is not readable.',msgFilePreviewAborted:'File preview aborted for "{name}".',msgFilePreviewError:'An error occurred while reading the file "{name}".',msgInvalidFileName:'Invalid or unsupported characters in file name "{name}".',msgInvalidFileType:'Invalid type for file "{name}". Only "{types}" files are supported.',msgInvalidFileExtension:'Invalid extension for file "{name}". Only "{extensions}" files are supported.',msgFileTypes:{image:"image",html:"HTML",text:"text",video:"video",audio:"audio",flash:"flash",pdf:"PDF",object:"object"},msgUploadAborted:"The file upload was aborted",msgUploadThreshold:"Processing...",msgUploadBegin:"Initializing...",msgUploadEnd:"Done",msgUploadEmpty:"No valid data available for upload.",msgUploadError:"Error",msgValidationError:"Validation Error",msgLoading:"Loading file {index} of {files} …",msgProgress:"Loading file {index} of {files} - {name} - {percent}% completed.",msgSelected:"{n} {files} selected",msgFoldersNotAllowed:"Drag & drop files only! {n} folder(s) dropped were skipped.",msgImageWidthSmall:'Width of image file "{name}" must be at least {size} px.',msgImageHeightSmall:'Height of image file "{name}" must be at least {size} px.',msgImageWidthLarge:'Width of image file "{name}" cannot exceed {size} px.',msgImageHeightLarge:'Height of image file "{name}" cannot exceed {size} px.',msgImageResizeError:"Could not get the image dimensions to resize.",msgImageResizeException:"Error while resizing the image.
    {errors}
    ",msgAjaxError:"Something went wrong with the {operation} operation. Please try again later!",msgAjaxProgressError:"{operation} failed",ajaxOperations:{deleteThumb:"file delete",uploadThumb:"file upload",uploadBatch:"batch file upload",uploadExtra:"form data upload"},dropZoneTitle:"Drag & drop files here …",dropZoneClickTitle:"
    (or click to select {files})",previewZoomButtonTitles:{prev:"View previous file",next:"View next file",toggleheader:"Toggle header",fullscreen:"Toggle full screen",borderless:"Toggle borderless mode",close:"Close detailed preview"}},e.fn.fileinput.Constructor=n,e(document).ready(function(){var t=e("input.file[type=file]");t.length&&t.fileinput()})},void 0===(o="function"==typeof i?i.apply(t,r):i)||(e.exports=o)}()},2125:(e,t,n)=>{n(4234),n(5303),n(8045),n(4355),n(7694),n(4912),n(5296),n(9898),n(4856),n(2208),n(9954),n(6159)},2208:(e,t,n)=>{!function(e){"use strict";function t(n,i){this.$body=e(document.body),this.$scrollElement=e(n).is(document.body)?e(window):e(n),this.options=e.extend({},t.DEFAULTS,i),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",e.proxy(this.process,this)),this.refresh(),this.process()}function n(n){return this.each(function(){var i=e(this),r=i.data("bs.scrollspy"),o="object"==typeof n&&n;r||i.data("bs.scrollspy",r=new t(this,o)),"string"==typeof n&&r[n]()})}t.VERSION="3.4.1",t.DEFAULTS={offset:10},t.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},t.prototype.refresh=function(){var t=this,n="offset",i=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),e.isWindow(this.$scrollElement[0])||(n="position",i=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var t=e(this),r=t.data("target")||t.attr("href"),o=/^#./.test(r)&&e(r);return o&&o.length&&o.is(":visible")&&[[o[n]().top+i,r]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},t.prototype.process=function(){var e,t=this.$scrollElement.scrollTop()+this.options.offset,n=this.getScrollHeight(),i=this.options.offset+n-this.$scrollElement.height(),r=this.offsets,o=this.targets,a=this.activeTarget;if(this.scrollHeight!=n&&this.refresh(),t>=i)return a!=(e=o[o.length-1])&&this.activate(e);if(a&&t=r[e]&&(void 0===r[e+1]||t{!function(e){"use strict";var t={script:[["lang",/(javascript|babel)/i,"javascript"],["type",/^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i,"javascript"],["type",/./,"text/plain"],[null,null,"javascript"]],style:[["lang",/^css$/i,"css"],["type",/^(text\/)?(x-)?(stylesheet|css)$/i,"css"],["type",/./,"text/plain"],[null,null,"css"]]};function n(e,t,n){var i=e.current(),r=i.search(t);return r>-1?e.backUp(i.length-r):i.match(/<\/?$/)&&(e.backUp(i.length),e.match(t,!1)||e.match(i)),n}var i={};function r(e){var t=i[e];return t||(i[e]=new RegExp("\\s+"+e+"\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*"))}function o(e,t){var n=e.match(r(t));return n?/^\s*(.*?)\s*$/.exec(n[2])[1]:""}function a(e,t){return new RegExp((t?"^":"")+"","i")}function s(e,t){for(var n in e)for(var i=t[n]||(t[n]=[]),r=e[n],o=r.length-1;o>=0;o--)i.unshift(r[o])}function l(e,t){for(var n=0;n=0;f--)c.script.unshift(["type",d[f].matches,d[f].mode]);function p(t,r){var s,u=o.token(t,r.htmlState),d=/\btag\b/.test(u);if(d&&!/[<>\s\/]/.test(t.current())&&(s=r.htmlState.tagName&&r.htmlState.tagName.toLowerCase())&&c.hasOwnProperty(s))r.inTag=s+" ";else if(r.inTag&&d&&/>$/.test(t.current())){var f=/^([\S]+) (.*)/.exec(r.inTag);r.inTag=null;var h=">"==t.current()&&l(c[f[1]],f[2]),g=e.getMode(i,h),m=a(f[1],!0),v=a(f[1],!1);r.token=function(e,t){return e.match(m,!1)?(t.token=p,t.localState=t.localMode=null,null):n(e,v,t.localMode.token(e,t.localState))},r.localMode=g,r.localState=e.startState(g,o.indent(r.htmlState,"",""))}else r.inTag&&(r.inTag+=t.current(),t.eol()&&(r.inTag+=" "));return u}return{startState:function(){return{token:p,inTag:null,localMode:null,localState:null,htmlState:e.startState(o)}},copyState:function(t){var n;return t.localState&&(n=e.copyState(t.localMode,t.localState)),{token:t.token,inTag:t.inTag,localMode:t.localMode,localState:n,htmlState:e.copyState(o,t.htmlState)}},token:function(e,t){return t.token(e,t)},indent:function(t,n,i){return!t.localMode||/^\s*<\//.test(n)?o.indent(t.htmlState,n,i):t.localMode.indent?t.localMode.indent(t.localState,n,i):e.Pass},innerMode:function(e){return{state:e.localState||e.htmlState,mode:e.localMode||o}}}},"xml","javascript","css"),e.defineMIME("text/html","htmlmixed")}(n(5237),n(576),n(6792),n(8656))},2669:(e,t,n)=>{"use strict";n.d(t,{A:()=>s});var i=n(1601),r=n.n(i),o=n(6314),a=n.n(o)()(r());a.push([e.id,'.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{background-color:transparent;border:none;font-size:1em}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline;list-style:none;padding:0}.select2-container .select2-selection--multiple .select2-selection__clear{background-color:transparent;border:none;font-size:1em}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;margin-left:5px;padding:0;max-width:100%;resize:none;height:18px;vertical-align:bottom;font-family:sans-serif;overflow:hidden;word-break:keep-all}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option--selectable{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;height:1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;height:26px;margin-right:20px;padding-right:0px}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;padding-bottom:5px;padding-right:5px;position:relative}.select2-container--default .select2-selection--multiple.select2-selection--clearable{padding-right:25px}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;font-weight:bold;height:20px;margin-right:10px;margin-top:5px;position:absolute;right:0;padding:1px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:inline-block;margin-left:5px;margin-top:5px;padding:0;padding-left:20px;position:relative;max-width:100%;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom;white-space:nowrap}.select2-container--default .select2-selection--multiple .select2-selection__choice__display{cursor:default;padding-left:2px;padding-right:5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{background-color:transparent;border:none;border-right:1px solid #aaa;border-top-left-radius:4px;border-bottom-left-radius:4px;color:#999;cursor:pointer;font-size:1em;font-weight:bold;padding:0 4px;position:absolute;left:0;top:0}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover,.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:focus{background-color:#f1f1f1;color:#333;outline:none}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__display{padding-left:5px;padding-right:2px}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{border-left:1px solid #aaa;border-right:none;border-top-left-radius:0;border-bottom-left-radius:0;border-top-right-radius:4px;border-bottom-right-radius:4px}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__clear{float:left;margin-left:10px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--group{padding:0}.select2-container--default .select2-results__option--disabled{color:#999}.select2-container--default .select2-results__option--selected{background-color:#ddd}.select2-container--default .select2-results__option--highlighted.select2-results__option--selectable{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=\'#FFFFFFFF\', endColorstr=\'#FFEEEEEE\', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;height:26px;margin-right:20px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=\'#FFEEEEEE\', endColorstr=\'#FFCCCCCC\', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=\'#FFFFFFFF\', endColorstr=\'#FFEEEEEE\', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=\'#FFEEEEEE\', endColorstr=\'#FFFFFFFF\', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0;padding-bottom:5px;padding-right:5px}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;display:inline-block;margin-left:5px;margin-top:5px;padding:0}.select2-container--classic .select2-selection--multiple .select2-selection__choice__display{cursor:default;padding-left:2px;padding-right:5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{background-color:transparent;border:none;border-top-left-radius:4px;border-bottom-left-radius:4px;color:#888;cursor:pointer;font-size:1em;font-weight:bold;padding:0 4px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555;outline:none}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__display{padding-left:5px;padding-right:2px}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{border-top-left-radius:0;border-bottom-left-radius:0;border-top-right-radius:4px;border-bottom-right-radius:4px}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option--group{padding:0}.select2-container--classic .select2-results__option--disabled{color:grey}.select2-container--classic .select2-results__option--highlighted.select2-results__option--selectable{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb}\n',""]);const s=a},3571:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>v});var i=n(5072),r=n.n(i),o=n(7825),a=n.n(o),s=n(7659),l=n.n(s),c=n(5056),u=n.n(c),d=n(540),f=n.n(d),p=n(1113),h=n.n(p),g=n(3906),m={};m.styleTagTransform=h(),m.setAttributes=u(),m.insert=l().bind(null,"head"),m.domAPI=a(),m.insertStyleElement=f();r()(g.A,m);const v=g.A&&g.A.locals?g.A.locals:void 0},3906:(e,t,n)=>{"use strict";n.d(t,{A:()=>h});var i=n(1601),r=n.n(i),o=n(6314),a=n.n(o),s=n(4417),l=n.n(s),c=new URL(n(3995),n.b),u=new URL(n(4004),n.b),d=a()(r()),f=l()(c),p=l()(u);d.push([e.id,`/*!\n * bootstrap-fileinput v4.4.4\n * http://plugins.krajee.com/file-input\n *\n * Krajee default styling for bootstrap-fileinput.\n *\n * Author: Kartik Visweswaran\n * Copyright: 2014 - 2017, Kartik Visweswaran, Krajee.com\n *\n * Licensed under the BSD 3-Clause\n * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md\n */\n.kv-hidden {\n display: none;\n}\n\n/* exif orientations */\n.rotate-2 {\n transform: rotateY(180deg);\n}\n\n.rotate-3 {\n transform: rotate(180deg);\n}\n\n.rotate-4 {\n transform: rotate(180deg) rotateY(180deg);\n}\n\n.rotate-5 {\n transform: rotate(270deg) rotateY(180deg);\n}\n\n.rotate-6 {\n transform: rotate(90deg);\n}\n\n.rotate-7 {\n transform: rotate(90deg) rotateY(180deg);\n}\n\n.rotate-8 {\n transform: rotate(270deg);\n}\n\n/* other styles */\ninput[type=file].file-loading, .file-loading input[type=file] {\n width: 0;\n height: 0;\n}\n\n.file-loading:before {\n content: " Loading...";\n display: inline-block;\n position: relative;\n padding-left: 20px;\n line-height: 16px;\n font-size: 13px;\n font-variant: small-caps;\n color: #999;\n background: transparent url(${f}) top left no-repeat;\n}\n\n.file-loading[dir=rtl]:before {\n background: transparent url(${f}) top right no-repeat;\n padding-left: 0;\n padding-right: 20px;\n}\n\n.file-object {\n margin: 0 0 -5px 0;\n padding: 0;\n}\n\n.btn-file {\n position: relative;\n overflow: hidden;\n}\n\n.btn-file input[type=file] {\n position: absolute;\n top: 0;\n right: 0;\n min-width: 100%;\n min-height: 100%;\n text-align: right;\n opacity: 0;\n background: none repeat scroll 0 0 transparent;\n cursor: inherit;\n display: block;\n}\n\n.file-caption-name {\n display: inline-block;\n overflow: hidden;\n height: 20px;\n word-break: break-all;\n}\n\n.input-group-lg .file-caption-name {\n height: 25px;\n}\n\n.file-error-message {\n color: #a94442;\n background-color: #f2dede;\n margin: 5px;\n border: 1px solid #ebccd1;\n border-radius: 4px;\n padding: 15px;\n}\n\n.file-error-message pre, .file-error-message ul {\n margin: 0;\n text-align: left;\n}\n\n.file-error-message pre {\n margin: 5px 0;\n}\n\n.file-caption-disabled {\n background-color: #eeeeee;\n cursor: not-allowed;\n opacity: 1;\n}\n\n.file-preview {\n border-radius: 5px;\n border: 1px solid #ddd;\n padding: 8px;\n width: 100%;\n margin-bottom: 5px;\n position: relative;\n}\n\n.file-preview .btn-xs {\n padding: 1px 5px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n\n.file-preview .fileinput-remove {\n position: absolute;\n top: 1px;\n right: 1px;\n line-height: 10px;\n}\n\n.file-preview-image {\n font: 40px Impact, Charcoal, sans-serif;\n color: green;\n}\n\n.krajee-default.file-preview-frame {\n position: relative;\n margin: 8px;\n border: 1px solid #ddd;\n box-shadow: 1px 1px 5px 0 #a2958a;\n padding: 6px;\n float: left;\n text-align: center;\n}\n\n.krajee-default.file-preview-frame:not(.file-preview-error):hover {\n box-shadow: 3px 3px 5px 0 #333;\n}\n\n.krajee-default.file-preview-frame .kv-file-content {\n width: 213px;\n height: 160px;\n}\n\n.krajee-default.file-preview-frame .file-thumbnail-footer {\n height: 70px;\n}\n\n.krajee-default .file-preview-text {\n display: block;\n color: #428bca;\n border: 1px solid #ddd;\n font-family: Menlo, Monaco, Consolas, "Courier New", monospace;\n outline: none;\n padding: 8px;\n resize: none;\n}\n\n.krajee-default .file-preview-html {\n border: 1px solid #ddd;\n padding: 8px;\n overflow: auto;\n}\n\n.krajee-default .file-preview-other:hover {\n opacity: 0.8;\n}\n\n.krajee-default .file-actions, .krajee-default .file-other-error {\n text-align: left;\n}\n\n.krajee-default .file-other-icon {\n font-size: 6em;\n}\n\n.krajee-default .file-footer-buttons {\n float: right;\n}\n\n.krajee-default .file-footer-caption {\n display: block;\n text-align: center;\n padding-top: 4px;\n font-size: 11px;\n color: #777;\n margin-bottom: 15px;\n}\n\n.krajee-default .file-preview-error {\n opacity: 0.65;\n box-shadow: none;\n}\n\n.krajee-default .file-preview-frame:not(.file-preview-error) .file-footer-caption:hover {\n color: #000;\n}\n\n.krajee-default .file-drag-handle, .krajee-default .file-upload-indicator {\n float: left;\n margin: 5px 0 -5px 0;\n width: 16px;\n height: 16px;\n}\n\n.krajee-default .file-thumb-progress {\n height: 11px;\n}\n\n.krajee-default .file-thumb-progress .progress, .krajee-default .file-thumb-progress .progress-bar {\n height: 11px;\n font-size: 9px;\n line-height: 10px;\n}\n\n.krajee-default .file-thumbnail-footer {\n position: relative;\n}\n\n.krajee-default .file-thumb-progress {\n position: absolute;\n top: 37px;\n left: 0;\n right: 0;\n}\n\n.krajee-default .file-caption-info,\n.krajee-default .file-size-info {\n display: block;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n width: 160px;\n height: 15px;\n margin: auto;\n}\n\n.krajee-default.kvsortable-ghost {\n background: #e1edf7;\n border: 2px solid #a1abff;\n}\n\n.kv-upload-progress .progress,\n.kv-upload-progress .progress-bar {\n height: 20px;\n line-height: 20px;\n}\n\n.kv-upload-progress .progress {\n margin: 10px 0;\n overflow: hidden;\n}\n\n/* noinspection CssOverwrittenProperties */\n.file-zoom-dialog .file-other-icon {\n font-size: 22em;\n font-size: 50vmin;\n}\n\n.file-input-new .file-preview, .file-input-new .close, .file-input-new .glyphicon-file,\n.file-input-new .fileinput-remove-button, .file-input-new .fileinput-upload-button,\n.file-input-ajax-new .fileinput-remove-button, .file-input-ajax-new .fileinput-upload-button {\n display: none;\n}\n\n.file-caption-main {\n width: 100%;\n}\n\n.file-input-ajax-new .no-browse .input-group-btn,\n.file-input-new .no-browse .input-group-btn {\n display: none;\n}\n\n.file-input-ajax-new .no-browse .form-control,\n.file-input-new .no-browse .form-control {\n border-top-right-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n\n.file-thumb-loading {\n background: transparent url(${f}) no-repeat scroll center center content-box !important;\n}\n\n.file-sortable .file-drag-handle {\n cursor: move;\n cursor: -webkit-grabbing;\n opacity: 1;\n}\n\n.file-sortable .file-drag-handle:hover {\n opacity: 0.7;\n}\n\n.file-drop-zone {\n border: 1px dashed #aaa;\n border-radius: 4px;\n height: 100%;\n text-align: center;\n vertical-align: middle;\n margin: 12px 15px 12px 12px;\n padding: 5px;\n}\n\n.file-drop-zone-title {\n color: #aaa;\n font-size: 1.6em;\n padding: 85px 10px;\n cursor: default;\n}\n\n.file-preview .clickable,\n.clickable .file-drop-zone-title {\n cursor: pointer;\n}\n\n.file-drop-zone.clickable:hover {\n border: 2px dashed #999;\n}\n\n.file-drop-zone.clickable:focus {\n border: 2px solid #5acde2;\n}\n\n.file-drop-zone .file-preview-thumbnails {\n cursor: default;\n}\n\n.file-highlighted {\n border: 2px dashed #999 !important;\n background-color: #f0f0f0;\n}\n\n.file-uploading {\n background: url(${p}) no-repeat center bottom 10px;\n opacity: 0.65;\n}\n\n.file-zoom-dialog .modal-dialog {\n position: relative;\n width: auto;\n}\n\n.file-zoom-dialog .modal-header {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: justify;\n justify-content: space-between;\n}\n\n.file-zoom-dialog .modal-header:before,\n.file-zoom-dialog .modal-header:after {\n display: none;\n}\n\n@media (min-width: 576px) {\n .file-zoom-dialog .modal-dialog {\n max-width: 500px;\n }\n}\n\n@media (min-width: 992px) {\n .file-zoom-dialog .modal-lg {\n max-width: 800px;\n }\n}\n\n.file-zoom-fullscreen.modal {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n}\n\n.file-zoom-fullscreen .modal-dialog {\n position: fixed;\n margin: 0;\n width: 100%;\n height: 100%;\n max-width: 100%;\n max-height: 100%;\n padding: 0;\n}\n\n.file-zoom-fullscreen .modal-content {\n border-radius: 0;\n box-shadow: none;\n}\n\n.file-zoom-fullscreen .modal-body {\n overflow-y: auto;\n}\n\n.file-zoom-dialog .btn-navigate {\n position: absolute;\n padding: 0;\n margin: 0;\n background: transparent;\n text-decoration: none;\n outline: none;\n opacity: 0.7;\n top: 45%;\n font-size: 4em;\n color: #1c94c4;\n}\n\n.file-zoom-dialog .floating-buttons {\n position: absolute;\n top: 5px;\n right: 10px;\n}\n\n.floating-buttons, .floating-buttons .btn {\n z-index: 3000;\n}\n\n.file-zoom-dialog .kv-zoom-actions .btn,\n.floating-buttons .btn {\n margin-left: 3px;\n}\n\n.file-zoom-dialog .btn-navigate:not([disabled]):hover {\n outline: none;\n box-shadow: none;\n opacity: 0.6;\n}\n\n.file-zoom-dialog .btn-navigate[disabled] {\n opacity: 0.3;\n}\n\n.file-zoom-dialog .btn-prev {\n left: 1px;\n}\n\n.file-zoom-dialog .btn-next {\n right: 1px;\n}\n\n.file-zoom-content {\n height: 480px;\n text-align: center;\n}\n\n.file-zoom-content .file-preview-image,\n.file-zoom-content .file-preview-video {\n max-height: 100%\n}\n\n.file-zoom-content > .file-object.type-image {\n width: auto;\n height: auto;\n min-height: inherit;\n max-width: 100%;\n max-height: 100%;\n}\n\n.file-zoom-content > .file-object.type-video,\n.file-zoom-content > .file-object.type-flash {\n width: auto;\n height: 100%;\n max-width: 100%;\n max-height: 100%;\n}\n\n.file-zoom-content > .file-object.type-audio {\n width: auto;\n height: 30px;\n}\n\n.file-zoom-content > .file-object.type-pdf,\n.file-zoom-content > .file-object.type-html,\n.file-zoom-content > .file-object.type-text,\n.file-zoom-content > .file-object.type-default {\n width: 100%;\n}\n\n.file-preview-initial.sortable-chosen {\n background-color: #d9edf7;\n}\n\n.hide-content .kv-file-content {\n display: none;\n}\n\n/* IE 10 fix */\n.btn-file ::-ms-browse {\n font-size: 10000px;\n width: 100%;\n height: 100%;\n}\n\n.file-zoom-content .is-portrait-gt4 {\n margin-top: 60px;\n}\n\n.file-zoom-dialog .kv-zoom-title {\n font-weight: 300;\n color: #999;\n}\n\n@media screen and (max-width: 767px) {\n .file-preview-thumbnails {\n display: flex;\n justify-content: center;\n align-items: center;\n flex-direction: column;\n }\n\n .file-zoom-dialog .modal-header {\n flex-direction: column;\n }\n}\n\n@media screen and (max-width: 350px) {\n .krajee-default.file-preview-frame .kv-file-content {\n width: 160px;\n }\n}`,""]);const h=d},3995:(e,t,n)=>{"use strict";e.exports=n.p+"0c63e069bbc944a0dd9e.gif"},4004:(e,t,n)=>{"use strict";e.exports=n.p+"5e6e572c071486f2318a.gif"},4234:(e,t,n)=>{!function(e){"use strict";e.fn.emulateTransitionEnd=function(t){var n=!1,i=this;e(this).one("bsTransitionEnd",function(){n=!0});return setTimeout(function(){n||e(i).trigger(e.support.transition.end)},t),this},e(function(){e.support.transition=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var n in t)if(void 0!==e.style[n])return{end:t[n]};return!1}(),e.support.transition&&(e.event.special.bsTransitionEnd={bindType:e.support.transition.end,delegateType:e.support.transition.end,handle:function(t){if(e(t.target).is(this))return t.handleObj.handler.apply(this,arguments)}})})}(n(4692))},4355:(e,t,n)=>{!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",e.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",e.proxy(this.pause,this)).on("mouseleave.bs.carousel",e.proxy(this.cycle,this))};function n(n){return this.each(function(){var i=e(this),r=i.data("bs.carousel"),o=e.extend({},t.DEFAULTS,i.data(),"object"==typeof n&&n),a="string"==typeof n?n:o.slide;r||i.data("bs.carousel",r=new t(this,o)),"number"==typeof n?r.to(n):a?r[a]():o.interval&&r.pause().cycle()})}t.VERSION="3.4.1",t.TRANSITION_DURATION=600,t.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},t.prototype.keydown=function(e){if(!/input|textarea/i.test(e.target.tagName)){switch(e.which){case 37:this.prev();break;case 39:this.next();break;default:return}e.preventDefault()}},t.prototype.cycle=function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},t.prototype.getItemIndex=function(e){return this.$items=e.parent().children(".item"),this.$items.index(e||this.$active)},t.prototype.getItemForDirection=function(e,t){var n=this.getItemIndex(t);if(("prev"==e&&0===n||"next"==e&&n==this.$items.length-1)&&!this.options.wrap)return t;var i=(n+("prev"==e?-1:1))%this.$items.length;return this.$items.eq(i)},t.prototype.to=function(e){var t=this,n=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(e>this.$items.length-1||e<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){t.to(e)}):n==e?this.pause().cycle():this.slide(e>n?"next":"prev",this.$items.eq(e))},t.prototype.pause=function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},t.prototype.next=function(){if(!this.sliding)return this.slide("next")},t.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},t.prototype.slide=function(n,i){var r=this.$element.find(".item.active"),o=i||this.getItemForDirection(n,r),a=this.interval,s="next"==n?"left":"right",l=this;if(o.hasClass("active"))return this.sliding=!1;var c=o[0],u=e.Event("slide.bs.carousel",{relatedTarget:c,direction:s});if(this.$element.trigger(u),!u.isDefaultPrevented()){if(this.sliding=!0,a&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var d=e(this.$indicators.children()[this.getItemIndex(o)]);d&&d.addClass("active")}var f=e.Event("slid.bs.carousel",{relatedTarget:c,direction:s});return e.support.transition&&this.$element.hasClass("slide")?(o.addClass(n),"object"==typeof o&&o.length&&o[0].offsetWidth,r.addClass(s),o.addClass(s),r.one("bsTransitionEnd",function(){o.removeClass([n,s].join(" ")).addClass("active"),r.removeClass(["active",s].join(" ")),l.sliding=!1,setTimeout(function(){l.$element.trigger(f)},0)}).emulateTransitionEnd(t.TRANSITION_DURATION)):(r.removeClass("active"),o.addClass("active"),this.sliding=!1,this.$element.trigger(f)),a&&this.cycle(),this}};var i=e.fn.carousel;e.fn.carousel=n,e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=i,this};var r=function(t){var i=e(this),r=i.attr("href");r&&(r=r.replace(/.*(?=#[^\s]+$)/,""));var o=i.attr("data-target")||r,a=e(document).find(o);if(a.hasClass("carousel")){var s=e.extend({},a.data(),i.data()),l=i.attr("data-slide-to");l&&(s.interval=!1),n.call(a,s),l&&a.data("bs.carousel").to(l),t.preventDefault()}};e(document).on("click.bs.carousel.data-api","[data-slide]",r).on("click.bs.carousel.data-api","[data-slide-to]",r),e(window).on("load",function(){e('[data-ride="carousel"]').each(function(){var t=e(this);n.call(t,t.data())})})}(n(4692))},4417:e=>{"use strict";e.exports=function(e,t){return t||(t={}),e?(e=String(e.__esModule?e.default:e),/^['"].*['"]$/.test(e)&&(e=e.slice(1,-1)),t.hash&&(e+=t.hash),/["'() \t\n]|(%20)/.test(e)||t.needQuotes?'"'.concat(e.replace(/"/g,'\\"').replace(/\n/g,"\\n"),'"'):e):e}},4692:function(e,t){var n;!function(t,n){"use strict";"object"==typeof e.exports?e.exports=t.document?n(t,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return n(e)}:n(t)}("undefined"!=typeof window?window:this,function(i,r){"use strict";var o=[],a=Object.getPrototypeOf,s=o.slice,l=o.flat?function(e){return o.flat.call(e)}:function(e){return o.concat.apply([],e)},c=o.push,u=o.indexOf,d={},f=d.toString,p=d.hasOwnProperty,h=p.toString,g=h.call(Object),m={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},b=function(e){return null!=e&&e===e.window},y=i.document,w={type:!0,src:!0,nonce:!0,noModule:!0};function x(e,t,n){var i,r,o=(n=n||y).createElement("script");if(o.text=e,t)for(i in w)(r=t[i]||t.getAttribute&&t.getAttribute(i))&&o.setAttribute(i,r);n.head.appendChild(o).parentNode.removeChild(o)}function C(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?d[f.call(e)]||"object":typeof e}var k="3.7.1",_=/HTML$/i,S=function(e,t){return new S.fn.init(e,t)};function T(e){var t=!!e&&"length"in e&&e.length,n=C(e);return!v(e)&&!b(e)&&("array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e)}function D(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}S.fn=S.prototype={jquery:k,constructor:S,length:0,toArray:function(){return s.call(this)},get:function(e){return null==e?s.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=S.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return S.each(this,e)},map:function(e){return this.pushStack(S.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(S.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(S.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n+~]|"+I+")"+I+"*"),H=new RegExp(I+"|>"),W=new RegExp(O),B=new RegExp("^"+P+"$"),U={ID:new RegExp("^#("+P+")"),CLASS:new RegExp("^\\.("+P+")"),TAG:new RegExp("^("+P+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+I+"*(even|odd|(([+-]|)(\\d*)n|)"+I+"*(?:([+-]|)"+I+"*(\\d+)|))"+I+"*\\)|)","i"),bool:new RegExp("^(?:"+T+")$","i"),needsContext:new RegExp("^"+I+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+I+"*((?:-\\d)?\\d*)"+I+"*\\)|)(?=[^-]|$)","i")},q=/^(?:input|select|textarea|button)$/i,V=/^h\d$/i,G=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,X=new RegExp("\\\\[\\da-fA-F]{1,6}"+I+"?|\\\\([^\\r\\n\\f])","g"),Z=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))},Y=function(){le()},J=fe(function(e){return!0===e.disabled&&D(e,"fieldset")},{dir:"parentNode",next:"legend"});try{g.apply(o=s.call(F.childNodes),F.childNodes),o[F.childNodes.length].nodeType}catch(e){g={apply:function(e,t){j.apply(e,s.call(t))},call:function(e){j.apply(e,s.call(arguments,1))}}}function Q(e,t,n,i){var r,o,a,s,c,u,p,h=t&&t.ownerDocument,b=t?t.nodeType:9;if(n=n||[],"string"!=typeof e||!e||1!==b&&9!==b&&11!==b)return n;if(!i&&(le(t),t=t||l,d)){if(11!==b&&(c=G.exec(e)))if(r=c[1]){if(9===b){if(!(a=t.getElementById(r)))return n;if(a.id===r)return g.call(n,a),n}else if(h&&(a=h.getElementById(r))&&Q.contains(t,a)&&a.id===r)return g.call(n,a),n}else{if(c[2])return g.apply(n,t.getElementsByTagName(e)),n;if((r=c[3])&&t.getElementsByClassName)return g.apply(n,t.getElementsByClassName(r)),n}if(!(k[e+" "]||f&&f.test(e))){if(p=e,h=t,1===b&&(H.test(e)||R.test(e))){for((h=K.test(e)&&se(t.parentNode)||t)==t&&m.scope||((s=t.getAttribute("id"))?s=S.escapeSelector(s):t.setAttribute("id",s=v)),o=(u=ue(e)).length;o--;)u[o]=(s?"#"+s:":scope")+" "+de(u[o]);p=u.join(",")}try{return g.apply(n,h.querySelectorAll(p)),n}catch(t){k(e,!0)}finally{s===v&&t.removeAttribute("id")}}}return be(e.replace($,"$1"),t,n,i)}function ee(){var e=[];return function n(i,r){return e.push(i+" ")>t.cacheLength&&delete n[e.shift()],n[i+" "]=r}}function te(e){return e[v]=!0,e}function ne(e){var t=l.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ie(e){return function(t){return D(t,"input")&&t.type===e}}function re(e){return function(t){return(D(t,"input")||D(t,"button"))&&t.type===e}}function oe(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&J(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function ae(e){return te(function(t){return t=+t,te(function(n,i){for(var r,o=e([],n.length,t),a=o.length;a--;)n[r=o[a]]&&(n[r]=!(i[r]=n[r]))})})}function se(e){return e&&void 0!==e.getElementsByTagName&&e}function le(e){var n,i=e?e.ownerDocument||e:F;return i!=l&&9===i.nodeType&&i.documentElement?(c=(l=i).documentElement,d=!S.isXMLDoc(l),h=c.matches||c.webkitMatchesSelector||c.msMatchesSelector,c.msMatchesSelector&&F!=l&&(n=l.defaultView)&&n.top!==n&&n.addEventListener("unload",Y),m.getById=ne(function(e){return c.appendChild(e).id=S.expando,!l.getElementsByName||!l.getElementsByName(S.expando).length}),m.disconnectedMatch=ne(function(e){return h.call(e,"*")}),m.scope=ne(function(){return l.querySelectorAll(":scope")}),m.cssHas=ne(function(){try{return l.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),m.getById?(t.filter.ID=function(e){var t=e.replace(X,Z);return function(e){return e.getAttribute("id")===t}},t.find.ID=function(e,t){if(void 0!==t.getElementById&&d){var n=t.getElementById(e);return n?[n]:[]}}):(t.filter.ID=function(e){var t=e.replace(X,Z);return function(e){var n=void 0!==e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},t.find.ID=function(e,t){if(void 0!==t.getElementById&&d){var n,i,r,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];for(r=t.getElementsByName(e),i=0;o=r[i++];)if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),t.find.TAG=function(e,t){return void 0!==t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},t.find.CLASS=function(e,t){if(void 0!==t.getElementsByClassName&&d)return t.getElementsByClassName(e)},f=[],ne(function(e){var t;c.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||f.push("\\["+I+"*(?:value|"+T+")"),e.querySelectorAll("[id~="+v+"-]").length||f.push("~="),e.querySelectorAll("a#"+v+"+*").length||f.push(".#.+[+~]"),e.querySelectorAll(":checked").length||f.push(":checked"),(t=l.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),c.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&f.push(":enabled",":disabled"),(t=l.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||f.push("\\["+I+"*name"+I+"*="+I+"*(?:''|\"\")")}),m.cssHas||f.push(":has"),f=f.length&&new RegExp(f.join("|")),_=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!m.sortDetached&&t.compareDocumentPosition(e)===n?e===l||e.ownerDocument==F&&Q.contains(F,e)?-1:t===l||t.ownerDocument==F&&Q.contains(F,t)?1:r?u.call(r,e)-u.call(r,t):0:4&n?-1:1)},l):l}for(e in Q.matches=function(e,t){return Q(e,null,null,t)},Q.matchesSelector=function(e,t){if(le(e),d&&!k[t+" "]&&(!f||!f.test(t)))try{var n=h.call(e,t);if(n||m.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){k(t,!0)}return Q(t,l,null,[e]).length>0},Q.contains=function(e,t){return(e.ownerDocument||e)!=l&&le(e),S.contains(e,t)},Q.attr=function(e,n){(e.ownerDocument||e)!=l&&le(e);var i=t.attrHandle[n.toLowerCase()],r=i&&p.call(t.attrHandle,n.toLowerCase())?i(e,n,!d):void 0;return void 0!==r?r:e.getAttribute(n)},Q.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},S.uniqueSort=function(e){var t,n=[],i=0,o=0;if(a=!m.sortStable,r=!m.sortStable&&s.call(e,0),E.call(e,_),a){for(;t=e[o++];)t===e[o]&&(i=n.push(o));for(;i--;)L.call(e,n[i],1)}return r=null,e},S.fn.uniqueSort=function(){return this.pushStack(S.uniqueSort(s.apply(this)))},t=S.expr={cacheLength:50,createPseudo:te,match:U,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(X,Z),e[3]=(e[3]||e[4]||e[5]||"").replace(X,Z),"~="===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]||Q.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]&&Q.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return U.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&W.test(n)&&(t=ue(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(X,Z).toLowerCase();return"*"===e?function(){return!0}:function(e){return D(e,t)}},CLASS:function(e){var t=w[e+" "];return t||(t=new RegExp("(^|"+I+")"+e+"("+I+"|$)"))&&w(e,function(e){return t.test("string"==typeof e.className&&e.className||void 0!==e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(i){var r=Q.attr(i,e);return null==r?"!="===t:!t||(r+="","="===t?r===n:"!="===t?r!==n:"^="===t?n&&0===r.indexOf(n):"*="===t?n&&r.indexOf(n)>-1:"$="===t?n&&r.slice(-n.length)===n:"~="===t?(" "+r.replace(N," ")+" ").indexOf(n)>-1:"|="===t&&(r===n||r.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,i,r){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===i&&0===r?function(e){return!!e.parentNode}:function(t,n,l){var c,u,d,f,p,h=o!==a?"nextSibling":"previousSibling",g=t.parentNode,m=s&&t.nodeName.toLowerCase(),y=!l&&!s,w=!1;if(g){if(o){for(;h;){for(d=t;d=d[h];)if(s?D(d,m):1===d.nodeType)return!1;p=h="only"===e&&!p&&"nextSibling"}return!0}if(p=[a?g.firstChild:g.lastChild],a&&y){for(w=(f=(c=(u=g[v]||(g[v]={}))[e]||[])[0]===b&&c[1])&&c[2],d=f&&g.childNodes[f];d=++f&&d&&d[h]||(w=f=0)||p.pop();)if(1===d.nodeType&&++w&&d===t){u[e]=[b,f,w];break}}else if(y&&(w=f=(c=(u=t[v]||(t[v]={}))[e]||[])[0]===b&&c[1]),!1===w)for(;(d=++f&&d&&d[h]||(w=f=0)||p.pop())&&(!(s?D(d,m):1===d.nodeType)||!++w||(y&&((u=d[v]||(d[v]={}))[e]=[b,w]),d!==t)););return(w-=r)===i||w%i===0&&w/i>=0}}},PSEUDO:function(e,n){var i,r=t.pseudos[e]||t.setFilters[e.toLowerCase()]||Q.error("unsupported pseudo: "+e);return r[v]?r(n):r.length>1?(i=[e,e,"",n],t.setFilters.hasOwnProperty(e.toLowerCase())?te(function(e,t){for(var i,o=r(e,n),a=o.length;a--;)e[i=u.call(e,o[a])]=!(t[i]=o[a])}):function(e){return r(e,0,i)}):r}},pseudos:{not:te(function(e){var t=[],n=[],i=ve(e.replace($,"$1"));return i[v]?te(function(e,t,n,r){for(var o,a=i(e,null,r,[]),s=e.length;s--;)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,r,o){return t[0]=e,i(t,null,o,n),t[0]=null,!n.pop()}}),has:te(function(e){return function(t){return Q(e,t).length>0}}),contains:te(function(e){return e=e.replace(X,Z),function(t){return(t.textContent||S.text(t)).indexOf(e)>-1}}),lang:te(function(e){return B.test(e||"")||Q.error("unsupported lang: "+e),e=e.replace(X,Z).toLowerCase(),function(t){var n;do{if(n=d?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(e){var t=i.location&&i.location.hash;return t&&t.slice(1)===e.id},root:function(e){return e===c},focus:function(e){return e===function(){try{return l.activeElement}catch(e){}}()&&l.hasFocus()&&!!(e.type||e.href||~e.tabIndex)},enabled:oe(!1),disabled:oe(!0),checked:function(e){return D(e,"input")&&!!e.checked||D(e,"option")&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!t.pseudos.empty(e)},header:function(e){return V.test(e.nodeName)},input:function(e){return q.test(e.nodeName)},button:function(e){return D(e,"input")&&"button"===e.type||D(e,"button")},text:function(e){var t;return D(e,"input")&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:ae(function(){return[0]}),last:ae(function(e,t){return[t-1]}),eq:ae(function(e,t,n){return[n<0?n+t:n]}),even:ae(function(e,t){for(var n=0;nt?t:n;--i>=0;)e.push(i);return e}),gt:ae(function(e,t,n){for(var i=n<0?n+t:n;++i1?function(t,n,i){for(var r=e.length;r--;)if(!e[r](t,n,i))return!1;return!0}:e[0]}function he(e,t,n,i,r){for(var o,a=[],s=0,l=e.length,c=null!=t;s-1&&(o[c]=!(a[c]=f))}}else p=he(p===a?p.splice(v,p.length):p),r?r(null,a,p,l):g.apply(a,p)})}function me(e){for(var i,r,o,a=e.length,s=t.relative[e[0].type],l=s||t.relative[" "],c=s?1:0,d=fe(function(e){return e===i},l,!0),f=fe(function(e){return u.call(i,e)>-1},l,!0),p=[function(e,t,r){var o=!s&&(r||t!=n)||((i=t).nodeType?d(e,t,r):f(e,t,r));return i=null,o}];c1&&pe(p),c>1&&de(e.slice(0,c-1).concat({value:" "===e[c-2].type?"*":""})).replace($,"$1"),r,c0,o=e.length>0,a=function(a,s,c,u,f){var p,h,m,v=0,y="0",w=a&&[],x=[],C=n,k=a||o&&t.find.TAG("*",f),_=b+=null==C?1:Math.random()||.1,T=k.length;for(f&&(n=s==l||s||f);y!==T&&null!=(p=k[y]);y++){if(o&&p){for(h=0,s||p.ownerDocument==l||(le(p),c=!d);m=e[h++];)if(m(p,s||l,c)){g.call(u,p);break}f&&(b=_)}r&&((p=!m&&p)&&v--,a&&w.push(p))}if(v+=y,r&&y!==v){for(h=0;m=i[h++];)m(w,x,s,c);if(a){if(v>0)for(;y--;)w[y]||x[y]||(x[y]=A.call(u));x=he(x)}g.apply(u,x),f&&!a&&x.length>0&&v+i.length>1&&S.uniqueSort(u)}return f&&(b=_,n=C),w};return r?te(a):a}(a,o)),s.selector=e}return s}function be(e,n,i,r){var o,a,s,l,c,u="function"==typeof e&&e,f=!r&&ue(e=u.selector||e);if(i=i||[],1===f.length){if((a=f[0]=f[0].slice(0)).length>2&&"ID"===(s=a[0]).type&&9===n.nodeType&&d&&t.relative[a[1].type]){if(!(n=(t.find.ID(s.matches[0].replace(X,Z),n)||[])[0]))return i;u&&(n=n.parentNode),e=e.slice(a.shift().value.length)}for(o=U.needsContext.test(e)?0:a.length;o--&&(s=a[o],!t.relative[l=s.type]);)if((c=t.find[l])&&(r=c(s.matches[0].replace(X,Z),K.test(a[0].type)&&se(n.parentNode)||n))){if(a.splice(o,1),!(e=r.length&&de(a)))return g.apply(i,r),i;break}}return(u||ve(e,f))(r,n,!d,i,!n||K.test(e)&&se(n.parentNode)||n),i}ce.prototype=t.filters=t.pseudos,t.setFilters=new ce,m.sortStable=v.split("").sort(_).join("")===v,le(),m.sortDetached=ne(function(e){return 1&e.compareDocumentPosition(l.createElement("fieldset"))}),S.find=Q,S.expr[":"]=S.expr.pseudos,S.unique=S.uniqueSort,Q.compile=ve,Q.select=be,Q.setDocument=le,Q.tokenize=ue,Q.escape=S.escapeSelector,Q.getText=S.text,Q.isXML=S.isXMLDoc,Q.selectors=S.expr,Q.support=S.support,Q.uniqueSort=S.uniqueSort}();var O=function(e,t,n){for(var i=[],r=void 0!==n;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(r&&S(e).is(n))break;i.push(e)}return i},N=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},z=S.expr.match.needsContext,R=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function H(e,t,n){return v(t)?S.grep(e,function(e,i){return!!t.call(e,i,e)!==n}):t.nodeType?S.grep(e,function(e){return e===t!==n}):"string"!=typeof t?S.grep(e,function(e){return u.call(t,e)>-1!==n}):S.filter(t,e,n)}S.filter=function(e,t,n){var i=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===i.nodeType?S.find.matchesSelector(i,e)?[i]:[]:S.find.matches(e,S.grep(t,function(e){return 1===e.nodeType}))},S.fn.extend({find:function(e){var t,n,i=this.length,r=this;if("string"!=typeof e)return this.pushStack(S(e).filter(function(){for(t=0;t1?S.uniqueSort(n):n},filter:function(e){return this.pushStack(H(this,e||[],!1))},not:function(e){return this.pushStack(H(this,e||[],!0))},is:function(e){return!!H(this,"string"==typeof e&&z.test(e)?S(e):e||[],!1).length}});var W,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var i,r;if(!e)return this;if(n=n||W,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:B.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:y,!0)),R.test(i[1])&&S.isPlainObject(t))for(i in t)v(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(r=y.getElementById(i[2]))&&(this[0]=r,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,W=S(y);var U=/^(?:parents|prev(?:Until|All))/,q={children:!0,contents:!0,next:!0,prev:!0};function V(e,t){for(;(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-1:1===n.nodeType&&S.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?S.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(S(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(S.uniqueSort(S.merge(this.get(),S(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),S.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return O(e,"parentNode")},parentsUntil:function(e,t,n){return O(e,"parentNode",n)},next:function(e){return V(e,"nextSibling")},prev:function(e){return V(e,"previousSibling")},nextAll:function(e){return O(e,"nextSibling")},prevAll:function(e){return O(e,"previousSibling")},nextUntil:function(e,t,n){return O(e,"nextSibling",n)},prevUntil:function(e,t,n){return O(e,"previousSibling",n)},siblings:function(e){return N((e.parentNode||{}).firstChild,e)},children:function(e){return N(e.firstChild)},contents:function(e){return null!=e.contentDocument&&a(e.contentDocument)?e.contentDocument:(D(e,"template")&&(e=e.content||e),S.merge([],e.childNodes))}},function(e,t){S.fn[e]=function(n,i){var r=S.map(this,t,n);return"Until"!==e.slice(-5)&&(i=n),i&&"string"==typeof i&&(r=S.filter(i,r)),this.length>1&&(q[e]||S.uniqueSort(r),U.test(e)&&r.reverse()),this.pushStack(r)}});var G=/[^\x20\t\r\n\f]+/g;function K(e){return e}function X(e){throw e}function Z(e,t,n,i){var r;try{e&&v(r=e.promise)?r.call(e).done(t).fail(n):e&&v(r=e.then)?r.call(e,t,n):t.apply(void 0,[e].slice(i))}catch(e){n.apply(void 0,[e])}}S.Callbacks=function(e){e="string"==typeof e?function(e){var t={};return S.each(e.match(G)||[],function(e,n){t[n]=!0}),t}(e):S.extend({},e);var t,n,i,r,o=[],a=[],s=-1,l=function(){for(r=r||e.once,i=t=!0;a.length;s=-1)for(n=a.shift();++s-1;)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?S.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return r=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return r=a=[],n||t||(o=n=""),this},locked:function(){return!!r},fireWith:function(e,n){return r||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||l()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!i}};return c},S.extend({Deferred:function(e){var t=[["notify","progress",S.Callbacks("memory"),S.Callbacks("memory"),2],["resolve","done",S.Callbacks("once memory"),S.Callbacks("once memory"),0,"resolved"],["reject","fail",S.Callbacks("once memory"),S.Callbacks("once memory"),1,"rejected"]],n="pending",r={state:function(){return n},always:function(){return o.done(arguments).fail(arguments),this},catch:function(e){return r.then(null,e)},pipe:function(){var e=arguments;return S.Deferred(function(n){S.each(t,function(t,i){var r=v(e[i[4]])&&e[i[4]];o[i[1]](function(){var e=r&&r.apply(this,arguments);e&&v(e.promise)?e.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[i[0]+"With"](this,r?[e]:arguments)})}),e=null}).promise()},then:function(e,n,r){var o=0;function a(e,t,n,r){return function(){var s=this,l=arguments,c=function(){var i,c;if(!(e=o&&(n!==X&&(s=void 0,l=[i]),t.rejectWith(s,l))}};e?u():(S.Deferred.getErrorHook?u.error=S.Deferred.getErrorHook():S.Deferred.getStackHook&&(u.error=S.Deferred.getStackHook()),i.setTimeout(u))}}return S.Deferred(function(i){t[0][3].add(a(0,i,v(r)?r:K,i.notifyWith)),t[1][3].add(a(0,i,v(e)?e:K)),t[2][3].add(a(0,i,v(n)?n:X))}).promise()},promise:function(e){return null!=e?S.extend(e,r):r}},o={};return S.each(t,function(e,i){var a=i[2],s=i[5];r[i[1]]=a.add,s&&a.add(function(){n=s},t[3-e][2].disable,t[3-e][3].disable,t[0][2].lock,t[0][3].lock),a.add(i[3].fire),o[i[0]]=function(){return o[i[0]+"With"](this===o?void 0:this,arguments),this},o[i[0]+"With"]=a.fireWith}),r.promise(o),e&&e.call(o,o),o},when:function(e){var t=arguments.length,n=t,i=Array(n),r=s.call(arguments),o=S.Deferred(),a=function(e){return function(n){i[e]=this,r[e]=arguments.length>1?s.call(arguments):n,--t||o.resolveWith(i,r)}};if(t<=1&&(Z(e,o.done(a(n)).resolve,o.reject,!t),"pending"===o.state()||v(r[n]&&r[n].then)))return o.then();for(;n--;)Z(r[n],a(n),o.reject);return o.promise()}});var Y=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;S.Deferred.exceptionHook=function(e,t){i.console&&i.console.warn&&e&&Y.test(e.name)&&i.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},S.readyException=function(e){i.setTimeout(function(){throw e})};var J=S.Deferred();function Q(){y.removeEventListener("DOMContentLoaded",Q),i.removeEventListener("load",Q),S.ready()}S.fn.ready=function(e){return J.then(e).catch(function(e){S.readyException(e)}),this},S.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--S.readyWait:S.isReady)||(S.isReady=!0,!0!==e&&--S.readyWait>0||J.resolveWith(y,[S]))}}),S.ready.then=J.then,"complete"===y.readyState||"loading"!==y.readyState&&!y.documentElement.doScroll?i.setTimeout(S.ready):(y.addEventListener("DOMContentLoaded",Q),i.addEventListener("load",Q));var ee=function(e,t,n,i,r,o,a){var s=0,l=e.length,c=null==n;if("object"===C(n))for(s in r=!0,n)ee(e,t,s,n[s],!0,o,a);else if(void 0!==i&&(r=!0,v(i)||(a=!0),c&&(a?(t.call(e,i),t=null):(c=t,t=function(e,t,n){return c.call(S(e),n)})),t))for(;s1,null,!0)},removeData:function(e){return this.each(function(){le.remove(this,e)})}}),S.extend({queue:function(e,t,n){var i;if(e)return t=(t||"fx")+"queue",i=se.get(e,t),n&&(!i||Array.isArray(n)?i=se.access(e,t,S.makeArray(n)):i.push(n)),i||[]},dequeue:function(e,t){t=t||"fx";var n=S.queue(e,t),i=n.length,r=n.shift(),o=S._queueHooks(e,t);"inprogress"===r&&(r=n.shift(),i--),r&&("fx"===t&&n.unshift("inprogress"),delete o.stop,r.call(e,function(){S.dequeue(e,t)},o)),!i&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return se.get(e,n)||se.access(e,n,{empty:S.Callbacks("once memory").add(function(){se.remove(e,[t+"queue",n])})})}}),S.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]*)/i,De=/^$|^module$|\/(?:java|ecma)script/i;ke=y.createDocumentFragment().appendChild(y.createElement("div")),(_e=y.createElement("input")).setAttribute("type","radio"),_e.setAttribute("checked","checked"),_e.setAttribute("name","t"),ke.appendChild(_e),m.checkClone=ke.cloneNode(!0).cloneNode(!0).lastChild.checked,ke.innerHTML="",m.noCloneChecked=!!ke.cloneNode(!0).lastChild.defaultValue,ke.innerHTML="",m.option=!!ke.lastChild;var Ae={thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};function Ee(e,t){var n;return n=void 0!==e.getElementsByTagName?e.getElementsByTagName(t||"*"):void 0!==e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&D(e,t)?S.merge([e],n):n}function Le(e,t){for(var n=0,i=e.length;n",""]);var Ie=/<|&#?\w+;/;function $e(e,t,n,i,r){for(var o,a,s,l,c,u,d=t.createDocumentFragment(),f=[],p=0,h=e.length;p-1)r&&r.push(o);else if(c=me(o),a=Ee(d.appendChild(o),"script"),c&&Le(a),n)for(u=0;o=a[u++];)De.test(o.type||"")&&n.push(o);return d}var Pe=/^([^.]*)(?:\.(.+)|)/;function Me(){return!0}function Fe(){return!1}function je(e,t,n,i,r,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(i=i||n,n=void 0),t)je(e,s,n,i,t[s],o);return e}if(null==i&&null==r?(r=n,i=n=void 0):null==r&&("string"==typeof n?(r=i,i=void 0):(r=i,i=n,n=void 0)),!1===r)r=Fe;else if(!r)return e;return 1===o&&(a=r,r=function(e){return S().off(e),a.apply(this,arguments)},r.guid=a.guid||(a.guid=S.guid++)),e.each(function(){S.event.add(this,t,r,i,n)})}function Oe(e,t,n){n?(se.set(e,t,!1),S.event.add(e,t,{namespace:!1,handler:function(e){var n,i=se.get(this,t);if(1&e.isTrigger&&this[t]){if(i)(S.event.special[t]||{}).delegateType&&e.stopPropagation();else if(i=s.call(arguments),se.set(this,t,i),this[t](),n=se.get(this,t),se.set(this,t,!1),i!==n)return e.stopImmediatePropagation(),e.preventDefault(),n}else i&&(se.set(this,t,S.event.trigger(i[0],i.slice(1),this)),e.stopPropagation(),e.isImmediatePropagationStopped=Me)}})):void 0===se.get(e,t)&&S.event.add(e,t,Me)}S.event={global:{},add:function(e,t,n,i,r){var o,a,s,l,c,u,d,f,p,h,g,m=se.get(e);if(oe(e))for(n.handler&&(n=(o=n).handler,r=o.selector),r&&S.find.matchesSelector(ge,r),n.guid||(n.guid=S.guid++),(l=m.events)||(l=m.events=Object.create(null)),(a=m.handle)||(a=m.handle=function(t){return void 0!==S&&S.event.triggered!==t.type?S.event.dispatch.apply(e,arguments):void 0}),c=(t=(t||"").match(G)||[""]).length;c--;)p=g=(s=Pe.exec(t[c])||[])[1],h=(s[2]||"").split(".").sort(),p&&(d=S.event.special[p]||{},p=(r?d.delegateType:d.bindType)||p,d=S.event.special[p]||{},u=S.extend({type:p,origType:g,data:i,handler:n,guid:n.guid,selector:r,needsContext:r&&S.expr.match.needsContext.test(r),namespace:h.join(".")},o),(f=l[p])||((f=l[p]=[]).delegateCount=0,d.setup&&!1!==d.setup.call(e,i,h,a)||e.addEventListener&&e.addEventListener(p,a)),d.add&&(d.add.call(e,u),u.handler.guid||(u.handler.guid=n.guid)),r?f.splice(f.delegateCount++,0,u):f.push(u),S.event.global[p]=!0)},remove:function(e,t,n,i,r){var o,a,s,l,c,u,d,f,p,h,g,m=se.hasData(e)&&se.get(e);if(m&&(l=m.events)){for(c=(t=(t||"").match(G)||[""]).length;c--;)if(p=g=(s=Pe.exec(t[c])||[])[1],h=(s[2]||"").split(".").sort(),p){for(d=S.event.special[p]||{},f=l[p=(i?d.delegateType:d.bindType)||p]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=f.length;o--;)u=f[o],!r&&g!==u.origType||n&&n.guid!==u.guid||s&&!s.test(u.namespace)||i&&i!==u.selector&&("**"!==i||!u.selector)||(f.splice(o,1),u.selector&&f.delegateCount--,d.remove&&d.remove.call(e,u));a&&!f.length&&(d.teardown&&!1!==d.teardown.call(e,h,m.handle)||S.removeEvent(e,p,m.handle),delete l[p])}else for(p in l)S.event.remove(e,p+t[c],n,i,!0);S.isEmptyObject(l)&&se.remove(e,"handle events")}},dispatch:function(e){var t,n,i,r,o,a,s=new Array(arguments.length),l=S.event.fix(e),c=(se.get(this,"events")||Object.create(null))[l.type]||[],u=S.event.special[l.type]||{};for(s[0]=l,t=1;t=1))for(;c!==this;c=c.parentNode||this)if(1===c.nodeType&&("click"!==e.type||!0!==c.disabled)){for(o=[],a={},n=0;n-1:S.find(r,this,null,[c]).length),a[r]&&o.push(i);o.length&&s.push({elem:c,handlers:o})}return c=this,l\s*$/g;function He(e,t){return D(e,"table")&&D(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function We(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Be(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Ue(e,t){var n,i,r,o,a,s;if(1===t.nodeType){if(se.hasData(e)&&(s=se.get(e).events))for(r in se.remove(t,"handle events"),s)for(n=0,i=s[r].length;n1&&"string"==typeof h&&!m.checkClone&&ze.test(h))return e.each(function(r){var o=e.eq(r);g&&(t[0]=h.call(this,r,o.html())),Ve(o,t,n,i)});if(f&&(o=(r=$e(t,e[0].ownerDocument,!1,e,i)).firstChild,1===r.childNodes.length&&(r=o),o||i)){for(s=(a=S.map(Ee(r,"script"),We)).length;d0&&Le(a,!l&&Ee(e,"script")),s},cleanData:function(e){for(var t,n,i,r=S.event.special,o=0;void 0!==(n=e[o]);o++)if(oe(n)){if(t=n[se.expando]){if(t.events)for(i in t.events)r[i]?S.event.remove(n,i):S.removeEvent(n,i,t.handle);n[se.expando]=void 0}n[le.expando]&&(n[le.expando]=void 0)}}}),S.fn.extend({detach:function(e){return Ge(this,e,!0)},remove:function(e){return Ge(this,e)},text:function(e){return ee(this,function(e){return void 0===e?S.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Ve(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||He(this,e).appendChild(e)})},prepend:function(){return Ve(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=He(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Ve(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Ve(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(S.cleanData(Ee(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return S.clone(this,e,t)})},html:function(e){return ee(this,function(e){var t=this[0]||{},n=0,i=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ne.test(e)&&!Ae[(Te.exec(e)||["",""])[1].toLowerCase()]){e=S.htmlPrefilter(e);try{for(;n=0&&(l+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-l-s-.5))||0),l+c}function ut(e,t,n){var i=Ze(e),r=(!m.boxSizingReliable()||n)&&"border-box"===S.css(e,"boxSizing",!1,i),o=r,a=Qe(e,t,i),s="offset"+t[0].toUpperCase()+t.slice(1);if(Ke.test(a)){if(!n)return a;a="auto"}return(!m.boxSizingReliable()&&r||!m.reliableTrDimensions()&&D(e,"tr")||"auto"===a||!parseFloat(a)&&"inline"===S.css(e,"display",!1,i))&&e.getClientRects().length&&(r="border-box"===S.css(e,"boxSizing",!1,i),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+ct(e,t,n||(r?"border":"content"),o,i,a)+"px"}function dt(e,t,n,i,r){return new dt.prototype.init(e,t,n,i,r)}S.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Qe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,aspectRatio:!0,borderImageSlice:!0,columnCount:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,scale:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeMiterlimit:!0,strokeOpacity:!0},cssProps:{},style:function(e,t,n,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var r,o,a,s=re(t),l=Xe.test(t),c=e.style;if(l||(t=rt(s)),a=S.cssHooks[t]||S.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(r=a.get(e,!1,i))?r:c[t];"string"===(o=typeof n)&&(r=pe.exec(n))&&r[1]&&(n=ye(e,t,r),o="number"),null!=n&&n==n&&("number"!==o||l||(n+=r&&r[3]||(S.cssNumber[s]?"":"px")),m.clearCloneStyle||""!==n||0!==t.indexOf("background")||(c[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,i))||(l?c.setProperty(t,n):c[t]=n))}},css:function(e,t,n,i){var r,o,a,s=re(t);return Xe.test(t)||(t=rt(s)),(a=S.cssHooks[t]||S.cssHooks[s])&&"get"in a&&(r=a.get(e,!0,n)),void 0===r&&(r=Qe(e,t,i)),"normal"===r&&t in st&&(r=st[t]),""===n||n?(o=parseFloat(r),!0===n||isFinite(o)?o||0:r):r}}),S.each(["height","width"],function(e,t){S.cssHooks[t]={get:function(e,n,i){if(n)return!ot.test(S.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?ut(e,t,i):Ye(e,at,function(){return ut(e,t,i)})},set:function(e,n,i){var r,o=Ze(e),a=!m.scrollboxSize()&&"absolute"===o.position,s=(a||i)&&"border-box"===S.css(e,"boxSizing",!1,o),l=i?ct(e,t,i,s,o):0;return s&&a&&(l-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-ct(e,t,"border",!1,o)-.5)),l&&(r=pe.exec(n))&&"px"!==(r[3]||"px")&&(e.style[t]=n,n=S.css(e,t)),lt(0,n,l)}}}),S.cssHooks.marginLeft=et(m.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Qe(e,"marginLeft"))||e.getBoundingClientRect().left-Ye(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),S.each({margin:"",padding:"",border:"Width"},function(e,t){S.cssHooks[e+t]={expand:function(n){for(var i=0,r={},o="string"==typeof n?n.split(" "):[n];i<4;i++)r[e+he[i]+t]=o[i]||o[i-2]||o[0];return r}},"margin"!==e&&(S.cssHooks[e+t].set=lt)}),S.fn.extend({css:function(e,t){return ee(this,function(e,t,n){var i,r,o={},a=0;if(Array.isArray(t)){for(i=Ze(e),r=t.length;a1)}}),S.Tween=dt,dt.prototype={constructor:dt,init:function(e,t,n,i,r,o){this.elem=e,this.prop=n,this.easing=r||S.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=i,this.unit=o||(S.cssNumber[n]?"":"px")},cur:function(){var e=dt.propHooks[this.prop];return e&&e.get?e.get(this):dt.propHooks._default.get(this)},run:function(e){var t,n=dt.propHooks[this.prop];return this.options.duration?this.pos=t=S.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):dt.propHooks._default.set(this),this}},dt.prototype.init.prototype=dt.prototype,dt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=S.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){S.fx.step[e.prop]?S.fx.step[e.prop](e):1!==e.elem.nodeType||!S.cssHooks[e.prop]&&null==e.elem.style[rt(e.prop)]?e.elem[e.prop]=e.now:S.style(e.elem,e.prop,e.now+e.unit)}}},dt.propHooks.scrollTop=dt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},S.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},S.fx=dt.prototype.init,S.fx.step={};var ft,pt,ht=/^(?:toggle|show|hide)$/,gt=/queueHooks$/;function mt(){pt&&(!1===y.hidden&&i.requestAnimationFrame?i.requestAnimationFrame(mt):i.setTimeout(mt,S.fx.interval),S.fx.tick())}function vt(){return i.setTimeout(function(){ft=void 0}),ft=Date.now()}function bt(e,t){var n,i=0,r={height:e};for(t=t?1:0;i<4;i+=2-t)r["margin"+(n=he[i])]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}function yt(e,t,n){for(var i,r=(wt.tweeners[t]||[]).concat(wt.tweeners["*"]),o=0,a=r.length;o1)},removeAttr:function(e){return this.each(function(){S.removeAttr(this,e)})}}),S.extend({attr:function(e,t,n){var i,r,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===e.getAttribute?S.prop(e,t,n):(1===o&&S.isXMLDoc(e)||(r=S.attrHooks[t.toLowerCase()]||(S.expr.match.bool.test(t)?xt:void 0)),void 0!==n?null===n?void S.removeAttr(e,t):r&&"set"in r&&void 0!==(i=r.set(e,n,t))?i:(e.setAttribute(t,n+""),n):r&&"get"in r&&null!==(i=r.get(e,t))?i:null==(i=S.find.attr(e,t))?void 0:i)},attrHooks:{type:{set:function(e,t){if(!m.radioValue&&"radio"===t&&D(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,i=0,r=t&&t.match(G);if(r&&1===e.nodeType)for(;n=r[i++];)e.removeAttribute(n)}}),xt={set:function(e,t,n){return!1===t?S.removeAttr(e,n):e.setAttribute(n,n),n}},S.each(S.expr.match.bool.source.match(/\w+/g),function(e,t){var n=Ct[t]||S.find.attr;Ct[t]=function(e,t,i){var r,o,a=t.toLowerCase();return i||(o=Ct[a],Ct[a]=r,r=null!=n(e,t,i)?a:null,Ct[a]=o),r}});var kt=/^(?:input|select|textarea|button)$/i,_t=/^(?:a|area)$/i;function St(e){return(e.match(G)||[]).join(" ")}function Tt(e){return e.getAttribute&&e.getAttribute("class")||""}function Dt(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(G)||[]}S.fn.extend({prop:function(e,t){return ee(this,S.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[S.propFix[e]||e]})}}),S.extend({prop:function(e,t,n){var i,r,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&S.isXMLDoc(e)||(t=S.propFix[t]||t,r=S.propHooks[t]),void 0!==n?r&&"set"in r&&void 0!==(i=r.set(e,n,t))?i:e[t]=n:r&&"get"in r&&null!==(i=r.get(e,t))?i:e[t]},propHooks:{tabIndex:{get:function(e){var t=S.find.attr(e,"tabindex");return t?parseInt(t,10):kt.test(e.nodeName)||_t.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),m.optSelected||(S.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),S.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){S.propFix[this.toLowerCase()]=this}),S.fn.extend({addClass:function(e){var t,n,i,r,o,a;return v(e)?this.each(function(t){S(this).addClass(e.call(this,t,Tt(this)))}):(t=Dt(e)).length?this.each(function(){if(i=Tt(this),n=1===this.nodeType&&" "+St(i)+" "){for(o=0;o-1;)n=n.replace(" "+r+" "," ");a=St(n),i!==a&&this.setAttribute("class",a)}}):this:this.attr("class","")},toggleClass:function(e,t){var n,i,r,o,a=typeof e,s="string"===a||Array.isArray(e);return v(e)?this.each(function(n){S(this).toggleClass(e.call(this,n,Tt(this),t),t)}):"boolean"==typeof t&&s?t?this.addClass(e):this.removeClass(e):(n=Dt(e),this.each(function(){if(s)for(o=S(this),r=0;r-1)return!0;return!1}});var At=/\r/g;S.fn.extend({val:function(e){var t,n,i,r=this[0];return arguments.length?(i=v(e),this.each(function(n){var r;1===this.nodeType&&(null==(r=i?e.call(this,n,S(this).val()):e)?r="":"number"==typeof r?r+="":Array.isArray(r)&&(r=S.map(r,function(e){return null==e?"":e+""})),(t=S.valHooks[this.type]||S.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,r,"value")||(this.value=r))})):r?(t=S.valHooks[r.type]||S.valHooks[r.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(r,"value"))?n:"string"==typeof(n=r.value)?n.replace(At,""):null==n?"":n:void 0}}),S.extend({valHooks:{option:{get:function(e){var t=S.find.attr(e,"value");return null!=t?t:St(S.text(e))}},select:{get:function(e){var t,n,i,r=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],l=a?o+1:r.length;for(i=o<0?l:a?o:0;i-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),S.each(["radio","checkbox"],function(){S.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=S.inArray(S(e).val(),t)>-1}},m.checkOn||(S.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Et=i.location,Lt={guid:Date.now()},It=/\?/;S.parseXML=function(e){var t,n;if(!e||"string"!=typeof e)return null;try{t=(new i.DOMParser).parseFromString(e,"text/xml")}catch(e){}return n=t&&t.getElementsByTagName("parsererror")[0],t&&!n||S.error("Invalid XML: "+(n?S.map(n.childNodes,function(e){return e.textContent}).join("\n"):e)),t};var $t=/^(?:focusinfocus|focusoutblur)$/,Pt=function(e){e.stopPropagation()};S.extend(S.event,{trigger:function(e,t,n,r){var o,a,s,l,c,u,d,f,h=[n||y],g=p.call(e,"type")?e.type:e,m=p.call(e,"namespace")?e.namespace.split("."):[];if(a=f=s=n=n||y,3!==n.nodeType&&8!==n.nodeType&&!$t.test(g+S.event.triggered)&&(g.indexOf(".")>-1&&(m=g.split("."),g=m.shift(),m.sort()),c=g.indexOf(":")<0&&"on"+g,(e=e[S.expando]?e:new S.Event(g,"object"==typeof e&&e)).isTrigger=r?2:3,e.namespace=m.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=n),t=null==t?[e]:S.makeArray(t,[e]),d=S.event.special[g]||{},r||!d.trigger||!1!==d.trigger.apply(n,t))){if(!r&&!d.noBubble&&!b(n)){for(l=d.delegateType||g,$t.test(l+g)||(a=a.parentNode);a;a=a.parentNode)h.push(a),s=a;s===(n.ownerDocument||y)&&h.push(s.defaultView||s.parentWindow||i)}for(o=0;(a=h[o++])&&!e.isPropagationStopped();)f=a,e.type=o>1?l:d.bindType||g,(u=(se.get(a,"events")||Object.create(null))[e.type]&&se.get(a,"handle"))&&u.apply(a,t),(u=c&&a[c])&&u.apply&&oe(a)&&(e.result=u.apply(a,t),!1===e.result&&e.preventDefault());return e.type=g,r||e.isDefaultPrevented()||d._default&&!1!==d._default.apply(h.pop(),t)||!oe(n)||c&&v(n[g])&&!b(n)&&((s=n[c])&&(n[c]=null),S.event.triggered=g,e.isPropagationStopped()&&f.addEventListener(g,Pt),n[g](),e.isPropagationStopped()&&f.removeEventListener(g,Pt),S.event.triggered=void 0,s&&(n[c]=s)),e.result}},simulate:function(e,t,n){var i=S.extend(new S.Event,n,{type:e,isSimulated:!0});S.event.trigger(i,null,t)}}),S.fn.extend({trigger:function(e,t){return this.each(function(){S.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return S.event.trigger(e,t,n,!0)}});var Mt=/\[\]$/,Ft=/\r?\n/g,jt=/^(?:submit|button|image|reset|file)$/i,Ot=/^(?:input|select|textarea|keygen)/i;function Nt(e,t,n,i){var r;if(Array.isArray(t))S.each(t,function(t,r){n||Mt.test(e)?i(e,r):Nt(e+"["+("object"==typeof r&&null!=r?t:"")+"]",r,n,i)});else if(n||"object"!==C(t))i(e,t);else for(r in t)Nt(e+"["+r+"]",t[r],n,i)}S.param=function(e,t){var n,i=[],r=function(e,t){var n=v(t)?t():t;i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!S.isPlainObject(e))S.each(e,function(){r(this.name,this.value)});else for(n in e)Nt(n,e[n],t,r);return i.join("&")},S.fn.extend({serialize:function(){return S.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=S.prop(this,"elements");return e?S.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!S(this).is(":disabled")&&Ot.test(this.nodeName)&&!jt.test(e)&&(this.checked||!Se.test(e))}).map(function(e,t){var n=S(this).val();return null==n?null:Array.isArray(n)?S.map(n,function(e){return{name:t.name,value:e.replace(Ft,"\r\n")}}):{name:t.name,value:n.replace(Ft,"\r\n")}}).get()}});var zt=/%20/g,Rt=/#.*$/,Ht=/([?&])_=[^&]*/,Wt=/^(.*?):[ \t]*([^\r\n]*)$/gm,Bt=/^(?:GET|HEAD)$/,Ut=/^\/\//,qt={},Vt={},Gt="*/".concat("*"),Kt=y.createElement("a");function Xt(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var i,r=0,o=t.toLowerCase().match(G)||[];if(v(n))for(;i=o[r++];)"+"===i[0]?(i=i.slice(1)||"*",(e[i]=e[i]||[]).unshift(n)):(e[i]=e[i]||[]).push(n)}}function Zt(e,t,n,i){var r={},o=e===Vt;function a(s){var l;return r[s]=!0,S.each(e[s]||[],function(e,s){var c=s(t,n,i);return"string"!=typeof c||o||r[c]?o?!(l=c):void 0:(t.dataTypes.unshift(c),a(c),!1)}),l}return a(t.dataTypes[0])||!r["*"]&&a("*")}function Yt(e,t){var n,i,r=S.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((r[n]?e:i||(i={}))[n]=t[n]);return i&&S.extend(!0,e,i),e}Kt.href=Et.href,S.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Et.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Et.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Gt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":S.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Yt(Yt(e,S.ajaxSettings),t):Yt(S.ajaxSettings,e)},ajaxPrefilter:Xt(qt),ajaxTransport:Xt(Vt),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var n,r,o,a,s,l,c,u,d,f,p=S.ajaxSetup({},t),h=p.context||p,g=p.context&&(h.nodeType||h.jquery)?S(h):S.event,m=S.Deferred(),v=S.Callbacks("once memory"),b=p.statusCode||{},w={},x={},C="canceled",k={readyState:0,getResponseHeader:function(e){var t;if(c){if(!a)for(a={};t=Wt.exec(o);)a[t[1].toLowerCase()+" "]=(a[t[1].toLowerCase()+" "]||[]).concat(t[2]);t=a[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return c?o:null},setRequestHeader:function(e,t){return null==c&&(e=x[e.toLowerCase()]=x[e.toLowerCase()]||e,w[e]=t),this},overrideMimeType:function(e){return null==c&&(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)k.always(e[k.status]);else for(t in e)b[t]=[b[t],e[t]];return this},abort:function(e){var t=e||C;return n&&n.abort(t),_(0,t),this}};if(m.promise(k),p.url=((e||p.url||Et.href)+"").replace(Ut,Et.protocol+"//"),p.type=t.method||t.type||p.method||p.type,p.dataTypes=(p.dataType||"*").toLowerCase().match(G)||[""],null==p.crossDomain){l=y.createElement("a");try{l.href=p.url,l.href=l.href,p.crossDomain=Kt.protocol+"//"+Kt.host!=l.protocol+"//"+l.host}catch(e){p.crossDomain=!0}}if(p.data&&p.processData&&"string"!=typeof p.data&&(p.data=S.param(p.data,p.traditional)),Zt(qt,p,t,k),c)return k;for(d in(u=S.event&&p.global)&&0===S.active++&&S.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Bt.test(p.type),r=p.url.replace(Rt,""),p.hasContent?p.data&&p.processData&&0===(p.contentType||"").indexOf("application/x-www-form-urlencoded")&&(p.data=p.data.replace(zt,"+")):(f=p.url.slice(r.length),p.data&&(p.processData||"string"==typeof p.data)&&(r+=(It.test(r)?"&":"?")+p.data,delete p.data),!1===p.cache&&(r=r.replace(Ht,"$1"),f=(It.test(r)?"&":"?")+"_="+Lt.guid+++f),p.url=r+f),p.ifModified&&(S.lastModified[r]&&k.setRequestHeader("If-Modified-Since",S.lastModified[r]),S.etag[r]&&k.setRequestHeader("If-None-Match",S.etag[r])),(p.data&&p.hasContent&&!1!==p.contentType||t.contentType)&&k.setRequestHeader("Content-Type",p.contentType),k.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Gt+"; q=0.01":""):p.accepts["*"]),p.headers)k.setRequestHeader(d,p.headers[d]);if(p.beforeSend&&(!1===p.beforeSend.call(h,k,p)||c))return k.abort();if(C="abort",v.add(p.complete),k.done(p.success),k.fail(p.error),n=Zt(Vt,p,t,k)){if(k.readyState=1,u&&g.trigger("ajaxSend",[k,p]),c)return k;p.async&&p.timeout>0&&(s=i.setTimeout(function(){k.abort("timeout")},p.timeout));try{c=!1,n.send(w,_)}catch(e){if(c)throw e;_(-1,e)}}else _(-1,"No Transport");function _(e,t,a,l){var d,f,y,w,x,C=t;c||(c=!0,s&&i.clearTimeout(s),n=void 0,o=l||"",k.readyState=e>0?4:0,d=e>=200&&e<300||304===e,a&&(w=function(e,t,n){for(var i,r,o,a,s=e.contents,l=e.dataTypes;"*"===l[0];)l.shift(),void 0===i&&(i=e.mimeType||t.getResponseHeader("Content-Type"));if(i)for(r in s)if(s[r]&&s[r].test(i)){l.unshift(r);break}if(l[0]in n)o=l[0];else{for(r in n){if(!l[0]||e.converters[r+" "+l[0]]){o=r;break}a||(a=r)}o=o||a}if(o)return o!==l[0]&&l.unshift(o),n[o]}(p,k,a)),!d&&S.inArray("script",p.dataTypes)>-1&&S.inArray("json",p.dataTypes)<0&&(p.converters["text script"]=function(){}),w=function(e,t,n,i){var r,o,a,s,l,c={},u=e.dataTypes.slice();if(u[1])for(a in e.converters)c[a.toLowerCase()]=e.converters[a];for(o=u.shift();o;)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!l&&i&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=u.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(!(a=c[l+" "+o]||c["* "+o]))for(r in c)if((s=r.split(" "))[1]===o&&(a=c[l+" "+s[0]]||c["* "+s[0]])){!0===a?a=c[r]:!0!==c[r]&&(o=s[0],u.unshift(s[1]));break}if(!0!==a)if(a&&e.throws)t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}(p,w,k,d),d?(p.ifModified&&((x=k.getResponseHeader("Last-Modified"))&&(S.lastModified[r]=x),(x=k.getResponseHeader("etag"))&&(S.etag[r]=x)),204===e||"HEAD"===p.type?C="nocontent":304===e?C="notmodified":(C=w.state,f=w.data,d=!(y=w.error))):(y=C,!e&&C||(C="error",e<0&&(e=0))),k.status=e,k.statusText=(t||C)+"",d?m.resolveWith(h,[f,C,k]):m.rejectWith(h,[k,C,y]),k.statusCode(b),b=void 0,u&&g.trigger(d?"ajaxSuccess":"ajaxError",[k,p,d?f:y]),v.fireWith(h,[k,C]),u&&(g.trigger("ajaxComplete",[k,p]),--S.active||S.event.trigger("ajaxStop")))}return k},getJSON:function(e,t,n){return S.get(e,t,n,"json")},getScript:function(e,t){return S.get(e,void 0,t,"script")}}),S.each(["get","post"],function(e,t){S[t]=function(e,n,i,r){return v(n)&&(r=r||i,i=n,n=void 0),S.ajax(S.extend({url:e,type:t,dataType:r,data:n,success:i},S.isPlainObject(e)&&e))}}),S.ajaxPrefilter(function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")}),S._evalUrl=function(e,t,n){return S.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){S.globalEval(e,t,n)}})},S.fn.extend({wrapAll:function(e){var t;return this[0]&&(v(e)&&(e=e.call(this[0])),t=S(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return v(e)?this.each(function(t){S(this).wrapInner(e.call(this,t))}):this.each(function(){var t=S(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v(e);return this.each(function(n){S(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){S(this).replaceWith(this.childNodes)}),this}}),S.expr.pseudos.hidden=function(e){return!S.expr.pseudos.visible(e)},S.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},S.ajaxSettings.xhr=function(){try{return new i.XMLHttpRequest}catch(e){}};var Jt={0:200,1223:204},Qt=S.ajaxSettings.xhr();m.cors=!!Qt&&"withCredentials"in Qt,m.ajax=Qt=!!Qt,S.ajaxTransport(function(e){var t,n;if(m.cors||Qt&&!e.crossDomain)return{send:function(r,o){var a,s=e.xhr();if(s.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(a in e.xhrFields)s[a]=e.xhrFields[a];for(a in e.mimeType&&s.overrideMimeType&&s.overrideMimeType(e.mimeType),e.crossDomain||r["X-Requested-With"]||(r["X-Requested-With"]="XMLHttpRequest"),r)s.setRequestHeader(a,r[a]);t=function(e){return function(){t&&(t=n=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Jt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=t(),n=s.onerror=s.ontimeout=t("error"),void 0!==s.onabort?s.onabort=n:s.onreadystatechange=function(){4===s.readyState&&i.setTimeout(function(){t&&n()})},t=t("abort");try{s.send(e.hasContent&&e.data||null)}catch(e){if(t)throw e}},abort:function(){t&&t()}}}),S.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),S.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return S.globalEval(e),e}}}),S.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),S.ajaxTransport("script",function(e){var t,n;if(e.crossDomain||e.scriptAttrs)return{send:function(i,r){t=S(" - \ No newline at end of file + diff --git a/src/com/fuse/actions/dashboard/ManagerDashboardCSV.java b/src/com/fuse/actions/dashboard/ManagerDashboardCSV.java index f02b07c4..12bdf09b 100644 --- a/src/com/fuse/actions/dashboard/ManagerDashboardCSV.java +++ b/src/com/fuse/actions/dashboard/ManagerDashboardCSV.java @@ -1,5 +1,16 @@ package com.fuse.actions.dashboard; +import com.fuse.actions.FSActionSupport; +import com.fuse.dao.Assessment; +import com.fuse.dao.AssessmentType; +import com.fuse.dao.Campaign; +import com.fuse.dao.CustomField; +import com.fuse.dao.CustomType; +import com.fuse.dao.RiskLevel; +import com.fuse.dao.Status; +import com.fuse.dao.Teams; +import com.fuse.dao.User; +import com.fuse.dao.Vulnerability; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.text.SimpleDateFormat; @@ -8,21 +19,10 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; - import org.apache.struts2.convention.annotation.Action; import org.apache.struts2.convention.annotation.Namespace; import org.apache.struts2.convention.annotation.Result; -import com.fuse.actions.FSActionSupport; -import com.fuse.dao.Assessment; -import com.fuse.dao.AssessmentType; -import com.fuse.dao.Campaign; -import com.fuse.dao.RiskLevel; -import com.fuse.dao.Status; -import com.fuse.dao.Teams; -import com.fuse.dao.User; -import com.fuse.dao.Vulnerability; - @Namespace("/portal") public class ManagerDashboardCSV extends FSActionSupport { @@ -47,11 +47,23 @@ public class ManagerDashboardCSV extends FSActionSupport { private List statuses; private List assessors; private List campaigns; - - @Action(value = "ManagerDashboardExportCSV", results = @Result(name = "success", type = "stream", params = { - "contentType", "text/csv", - "inputName", "inputStream", - "contentDisposition", "attachment;filename=\"${filename}\"" })) + private List assessmentCustomTypes; + + @Action( + value = "ManagerDashboardExportCSV", + results = @Result( + name = "success", + type = "stream", + params = { + "contentType", + "text/csv", + "inputName", + "inputStream", + "contentDisposition", + "attachment;filename=\"${filename}\"", + } + ) + ) public String exportCSV() { // Check if user has manager role if (!this.isAcmanager()) { @@ -69,7 +81,19 @@ public String exportCSV() { // Create header with individual vulnerability severity columns StringBuilder header = new StringBuilder(); - header.append("AppId,Name,Type,Team,Assessor,Start Date,End Date,Completed Date,Status"); + header.append( + "AppId,Name,Type,Team,Assessor,Start Date,End Date,Completed Date,Status" + ); + + // Add custom field columns to header + for (CustomType customType : assessmentCustomTypes) { + if ( + customType.getKey() != null && + !customType.getKey().trim().isEmpty() + ) { + header.append(",").append(escapeCSV(customType.getKey())); + } + } // Add severity level columns to header for (RiskLevel level : riskLevels) { @@ -85,7 +109,13 @@ public String exportCSV() { for (Assessment asmt : searchResults) { csvContent.append(escapeCSV(asmt.getAppId())).append(","); csvContent.append(escapeCSV(asmt.getName())).append(","); - csvContent.append(escapeCSV(asmt.getType() != null ? asmt.getType().getType() : "")).append(","); + csvContent + .append( + escapeCSV( + asmt.getType() != null ? asmt.getType().getType() : "" + ) + ) + .append(","); // Get team name from first assessor String teamName = ""; @@ -101,30 +131,64 @@ public String exportCSV() { StringBuilder assessorNames = new StringBuilder(); if (asmt.getAssessor() != null) { for (int i = 0; i < asmt.getAssessor().size(); i++) { - if (i > 0) - assessorNames.append(", "); + if (i > 0) assessorNames.append(", "); User assessor = asmt.getAssessor().get(i); - assessorNames.append(assessor.getFname()).append(" ").append(assessor.getLname()); + assessorNames + .append(assessor.getFname()) + .append(" ") + .append(assessor.getLname()); } } csvContent.append(escapeCSV(assessorNames.toString())).append(","); // Add dates - csvContent.append(asmt.getStart() != null ? dateFormat.format(asmt.getStart()) : "").append(","); - csvContent.append(asmt.getEnd() != null ? dateFormat.format(asmt.getEnd()) : "").append(","); - csvContent.append(asmt.getCompleted() != null ? dateFormat.format(asmt.getCompleted()) : "").append(","); + csvContent + .append( + asmt.getStart() != null + ? dateFormat.format(asmt.getStart()) + : "" + ) + .append(","); + csvContent + .append( + asmt.getEnd() != null + ? dateFormat.format(asmt.getEnd()) + : "" + ) + .append(","); + csvContent + .append( + asmt.getCompleted() != null + ? dateFormat.format(asmt.getCompleted()) + : "" + ) + .append(","); csvContent.append(escapeCSV(asmt.getStatus())); + // Add custom field values + for (CustomType customType : assessmentCustomTypes) { + String customFieldValue = getPopulatedCustomFieldValue( + asmt.getCustomFields(), + customType + ); + csvContent.append(",").append(escapeCSV(customFieldValue)); + } + // Get vulnerability findings count - List vulns = em.createQuery( - "from Vulnerability where assessmentId = :aid", Vulnerability.class) - .setParameter("aid", asmt.getId()) - .getResultList(); + List vulns = em + .createQuery( + "from Vulnerability where assessmentId = :aid", + Vulnerability.class + ) + .setParameter("aid", asmt.getId()) + .getResultList(); // Count by severity Map findings = new LinkedHashMap<>(); for (RiskLevel level : riskLevels) { - if (level.getRisk() != null && !level.getRisk().trim().isEmpty()) { + if ( + level.getRisk() != null && !level.getRisk().trim().isEmpty() + ) { findings.put(level.getRisk(), 0); } } @@ -132,15 +196,23 @@ public String exportCSV() { for (Vulnerability vuln : vulns) { if (vuln.getOverall() != null) { String severityName = getRiskLevelName(vuln.getOverall()); - if (severityName != null && !severityName.equals("Unassigned")) { - findings.put(severityName, findings.getOrDefault(severityName, 0) + 1); + if ( + severityName != null && + !severityName.equals("Unassigned") + ) { + findings.put( + severityName, + findings.getOrDefault(severityName, 0) + 1 + ); } } } // Add vulnerability counts in separate columns for (RiskLevel level : riskLevels) { - if (level.getRisk() != null && !level.getRisk().trim().isEmpty()) { + if ( + level.getRisk() != null && !level.getRisk().trim().isEmpty() + ) { Integer count = findings.get(level.getRisk()); csvContent.append(",").append(count != null ? count : 0); } @@ -149,38 +221,97 @@ public String exportCSV() { } // Set up the download - inputStream = new ByteArrayInputStream(csvContent.toString().getBytes()); - SimpleDateFormat filenameFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); - filename = "manager_dashboard_assessments_" + filenameFormat.format(new Date()) + ".csv"; + inputStream = new ByteArrayInputStream( + csvContent.toString().getBytes() + ); + SimpleDateFormat filenameFormat = new SimpleDateFormat( + "yyyyMMdd_HHmmss" + ); + filename = + "manager_dashboard_assessments_" + + filenameFormat.format(new Date()) + + ".csv"; return SUCCESS; } - private String escapeCSV(String value) { - if (value == null) + /** + * Returns the value actually populated on the entity for the given custom type, + * or an empty string. The custom type's configured default is never emitted: a + * field whose stored value still equals the type default is treated as not + * populated. This also covers unselected dropdowns, whose stored value is the + * full comma-separated option list held in the type's defaultValue. + */ + private String getPopulatedCustomFieldValue( + List customFields, + CustomType customType + ) { + if (customFields == null) { return ""; + } + for (CustomField cf : customFields) { + if ( + cf.getType() != null && + cf.getType().getId().equals(customType.getId()) + ) { + String value = cf.getValue(); + if (value == null || value.isEmpty()) { + return ""; + } + String defaultValue = customType.getDefaultValue(); + if (defaultValue != null && value.equals(defaultValue)) { + return ""; + } + return value; + } + } + return ""; + } + + private String escapeCSV(String value) { + if (value == null) return ""; // Escape quotes by doubling them and wrap in quotes if contains comma, quote, // or newline - if (value.contains(",") || value.contains("\"") || value.contains("\n")) { + if ( + value.contains(",") || value.contains("\"") || value.contains("\n") + ) { value = "\"" + value.replace("\"", "\"\"") + "\""; } return value; } private void loadDropdownData() { - assessmentTypes = em.createQuery("from AssessmentType order by type").getResultList(); + assessmentTypes = em + .createQuery("from AssessmentType order by type") + .getResultList(); teams = em.createQuery("from Teams order by TeamName").getResultList(); - riskLevels = em.createQuery("from RiskLevel order by riskId desc").getResultList(); + riskLevels = em + .createQuery("from RiskLevel order by riskId desc") + .getResultList(); statuses = em.createQuery("from Status order by name").getResultList(); // Get all users with assessor permission - assessors = em.createQuery("from User order by lname, fname", User.class) - .getResultList() - .stream() - .filter(a -> a.getPermissions() != null && a.getPermissions().isAssessor()) - .collect(Collectors.toList()); + assessors = em + .createQuery("from User order by lname, fname", User.class) + .getResultList() + .stream() + .filter( + a -> + a.getPermissions() != null && + a.getPermissions().isAssessor() + ) + .collect(Collectors.toList()); // Get all campaigns - campaigns = em.createQuery("from Campaign order by name").getResultList(); + campaigns = em + .createQuery("from Campaign order by name") + .getResultList(); + // Get all assessment custom types (type 0 = ASMT, fieldType < 3 excludes forms) + assessmentCustomTypes = em + .createQuery( + "from CustomType where type = 0 and fieldType < 3 and deleted = false order by key", + CustomType.class + ) + .getResultList(); } private List performAssessmentSearch() { @@ -192,12 +323,17 @@ private List performAssessmentSearch() { // Build date range condition - include assessments that overlap with the search // range if (startDate != null && endDate != null) { - endDate.setDate(endDate.getDate()+1); + endDate.setDate(endDate.getDate() + 1); query.append("$and: ["); - query.append(" { \"start\": {$lte: ISODate(\"").append(sdf.format(endDate)).append("\")}}, "); + query + .append(" { \"start\": {$lte: ISODate(\"") + .append(sdf.format(endDate)) + .append("\")}}, "); query.append(" { $or: [ "); - query.append(" { \"completed\": { $exists: true, $gte: ISODate(\"") - .append(sdf.format(startDate)).append("\")}}"); + query + .append(" { \"completed\": { $exists: true, $gte: ISODate(\"") + .append(sdf.format(startDate)) + .append("\")}}"); query.append(" ,"); query.append(" { \"completed\": {$exists: false}},"); query.append(" ]}"); @@ -207,8 +343,7 @@ private List performAssessmentSearch() { // Build assessment type condition if (typeId != null && typeId > 0) { - if (hasConditions) - query.append(", "); + if (hasConditions) query.append(", "); query.append("\"type_id\": ").append(typeId); hasConditions = true; } @@ -221,16 +356,16 @@ private List performAssessmentSearch() { if (team != null) { // Use a native MongoDB query to find users with this team String userQuery = "{\"team_id\": " + teamId + "}"; - List teamUsers = em.createNativeQuery(userQuery, User.class).getResultList(); + List teamUsers = em + .createNativeQuery(userQuery, User.class) + .getResultList(); if (!teamUsers.isEmpty()) { - if (hasConditions) - query.append(", "); + if (hasConditions) query.append(", "); query.append("\"assessor\": {$in: ["); boolean first = true; for (User user : teamUsers) { - if (!first) - query.append(", "); + if (!first) query.append(", "); query.append(user.getId()); first = false; } @@ -242,23 +377,23 @@ private List performAssessmentSearch() { // Build assessor condition if (assessorId != null && assessorId > 0) { - if (hasConditions) - query.append(", "); + if (hasConditions) query.append(", "); query.append("\"assessor\": ").append(assessorId); hasConditions = true; } // Build campaign condition if (campaignId != null && campaignId > 0) { - if (hasConditions) - query.append(", "); + if (hasConditions) query.append(", "); query.append("\"campaign_id\": ").append(campaignId); hasConditions = true; } query.append("}"); // Execute the native MongoDB query - List mongoResults = em.createNativeQuery(query.toString(), Assessment.class).getResultList(); + List mongoResults = em + .createNativeQuery(query.toString(), Assessment.class) + .getResultList(); // Filter Results based on status List searchResults; @@ -274,9 +409,10 @@ private List performAssessmentSearch() { if (statusName != null) { final String finalStatusName = statusName; - searchResults = mongoResults.stream() - .filter(a -> finalStatusName.equals(a.getStatus())) - .collect(Collectors.toList()); + searchResults = mongoResults + .stream() + .filter(a -> finalStatusName.equals(a.getStatus())) + .collect(Collectors.toList()); } else { searchResults = mongoResults; } @@ -286,12 +422,9 @@ private List performAssessmentSearch() { // Sort by start date descending searchResults.sort((a1, a2) -> { - if (a1.getStart() == null && a2.getStart() == null) - return 0; - if (a1.getStart() == null) - return 1; - if (a2.getStart() == null) - return -1; + if (a1.getStart() == null && a2.getStart() == null) return 0; + if (a1.getStart() == null) return 1; + if (a2.getStart() == null) return -1; return a2.getStart().compareTo(a1.getStart()); }); @@ -299,8 +432,7 @@ private List performAssessmentSearch() { } private String getRiskLevelName(Long riskId) { - if (riskId == null) - return "Unassigned"; + if (riskId == null) return "Unassigned"; for (RiskLevel level : riskLevels) { if (riskId.equals(Long.valueOf(level.getRiskId()))) { @@ -390,4 +522,4 @@ public String getSearchAction() { public void setSearchAction(String searchAction) { this.searchAction = searchAction; } -} \ No newline at end of file +} diff --git a/src/com/fuse/actions/dashboard/ManagerDashboardVulnerabilitiesCSV.java b/src/com/fuse/actions/dashboard/ManagerDashboardVulnerabilitiesCSV.java new file mode 100644 index 00000000..2ed6c53d --- /dev/null +++ b/src/com/fuse/actions/dashboard/ManagerDashboardVulnerabilitiesCSV.java @@ -0,0 +1,483 @@ +package com.fuse.actions.dashboard; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.struts2.convention.annotation.Action; +import org.apache.struts2.convention.annotation.Namespace; +import org.apache.struts2.convention.annotation.Result; + +import com.fuse.actions.FSActionSupport; +import com.fuse.dao.Assessment; +import com.fuse.dao.AssessmentType; +import com.fuse.dao.Campaign; +import com.fuse.dao.CustomField; +import com.fuse.dao.CustomType; +import com.fuse.dao.RiskLevel; +import com.fuse.dao.Status; +import com.fuse.dao.Teams; +import com.fuse.dao.User; +import com.fuse.dao.Vulnerability; + +@Namespace("/portal") +public class ManagerDashboardVulnerabilitiesCSV extends FSActionSupport { + + // Properties for CSV download + private InputStream inputStream; + private String filename; + + // Search parameters (same as ManagerDashboard) + private Date startDate; + private Date endDate; + private Long typeId; + private Long teamId; + private String status; + private Long assessorId; + private Long campaignId; + private String searchAction = "search"; // Always search mode for CSV + + // Lists for dropdowns + private List assessmentTypes; + private List teams; + private List riskLevels; + private List statuses; + private List assessors; + private List campaigns; + private List assessmentCustomTypes; + private List vulnerabilityCustomTypes; + + @Action(value = "ManagerDashboardExportVulnerabilitiesCSV", results = @Result(name = "success", type = "stream", params = { + "contentType", "text/csv", + "inputName", "inputStream", + "contentDisposition", "attachment;filename=\"${filename}\"" })) + public String exportCSV() { + // Check if user has manager role + if (!this.isAcmanager()) { + return LOGIN; + } + + // Load dropdown data needed for search + loadDropdownData(); + + // Perform the assessment search using the same logic as ManagerDashboard + List searchResults = performAssessmentSearch(); + + // Generate CSV content + StringBuilder csvContent = new StringBuilder(); + + // Create header + StringBuilder header = new StringBuilder(); + header.append("Vulnerability ID,Vulnerability Name,Assessment ID,Assessment Name,Assessment AppId,"); + header.append("Assessment Type"); + + // Add assessment custom field columns to header (non-richtext) + for (CustomType customType : assessmentCustomTypes) { + if (customType.getKey() != null && !customType.getKey().trim().isEmpty()) { + header.append(",").append(escapeCSV(customType.getKey())); + } + } + + header.append(",Team,Assessor,Severity,CVSS Score,Category,"); + header.append("Opened Date,Closed Date,Status,Tracking ID"); + + // Add vulnerability custom field columns to header + for (CustomType customType : vulnerabilityCustomTypes) { + if (customType.getKey() != null && !customType.getKey().trim().isEmpty()) { + header.append(",").append(escapeCSV(customType.getKey())); + } + } + + header.append("\n"); + csvContent.append(header); + + // Add data rows - iterate through assessments and their vulnerabilities + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + int vulnCount = 0; + + for (Assessment asmt : searchResults) { + // Get vulnerabilities for this assessment that were opened in the date range + List vulns = getVulnerabilitiesInDateRange(asmt.getId()); + + for (Vulnerability vuln : vulns) { + vulnCount++; + + // Vulnerability ID and Name + csvContent.append(escapeCSV(String.valueOf(vuln.getId()))).append(","); + csvContent.append(escapeCSV(vuln.getName())).append(","); + + // Assessment info + csvContent.append(escapeCSV(String.valueOf(asmt.getId()))).append(","); + csvContent.append(escapeCSV(asmt.getName())).append(","); + csvContent.append(escapeCSV(asmt.getAppId())).append(","); + csvContent.append(escapeCSV(asmt.getType() != null ? asmt.getType().getType() : "")).append(","); + + // Add assessment custom field values (non-richtext) + for (CustomType customType : assessmentCustomTypes) { + String customFieldValue = getPopulatedCustomFieldValue(asmt.getCustomFields(), customType); + csvContent.append(escapeCSV(customFieldValue)).append(","); + } + + // Get team name from first assessor + String teamName = ""; + if (asmt.getAssessor() != null && !asmt.getAssessor().isEmpty()) { + User firstAssessor = asmt.getAssessor().get(0); + if (firstAssessor.getTeam() != null) { + teamName = firstAssessor.getTeam().getTeamName(); + } + } + csvContent.append(escapeCSV(teamName)).append(","); + + // Get assessor names + StringBuilder assessorNames = new StringBuilder(); + if (asmt.getAssessor() != null) { + for (int i = 0; i < asmt.getAssessor().size(); i++) { + if (i > 0) + assessorNames.append(", "); + User assessor = asmt.getAssessor().get(i); + assessorNames.append(assessor.getFname()).append(" ").append(assessor.getLname()); + } + } + csvContent.append(escapeCSV(assessorNames.toString())).append(","); + + // Severity + String severityName = getRiskLevelName(vuln.getOverall()); + csvContent.append(escapeCSV(severityName)).append(","); + + // CVSS Score + csvContent.append(escapeCSV(vuln.getCvssScore() != null ? vuln.getCvssScore() : "")).append(","); + + // Category + csvContent.append(escapeCSV(vuln.getCategory() != null ? vuln.getCategory().getName() : "")).append(","); + + // Dates + csvContent.append(vuln.getOpened() != null ? dateFormat.format(vuln.getOpened()) : "").append(","); + csvContent.append(vuln.getClosed() != null ? dateFormat.format(vuln.getClosed()) : "").append(","); + + // Status + String vulnStatus = vuln.getClosed() != null ? "Closed" : "Open"; + csvContent.append(escapeCSV(vulnStatus)).append(","); + + // Tracking ID + csvContent.append(escapeCSV(vuln.getTracking() != null ? vuln.getTracking() : "")); + + // Add vulnerability custom field values + for (CustomType customType : vulnerabilityCustomTypes) { + String customFieldValue = getPopulatedCustomFieldValue(vuln.getCustomFields(), customType); + csvContent.append(",").append(escapeCSV(customFieldValue)); + } + + csvContent.append("\n"); + } + } + + // Set up the download + inputStream = new ByteArrayInputStream(csvContent.toString().getBytes()); + SimpleDateFormat filenameFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); + filename = "manager_dashboard_vulnerabilities_" + filenameFormat.format(new Date()) + ".csv"; + + return SUCCESS; + } + + private List getVulnerabilitiesInDateRange(Long assessmentId) { + // Get all vulnerabilities for this assessment + List allVulns = em.createQuery( + "from Vulnerability where assessmentId = :aid", Vulnerability.class) + .setParameter("aid", assessmentId) + .getResultList(); + + // Filter by opened date if date range is specified + if (startDate != null && endDate != null) { + Date adjustedEndDate = new Date(endDate.getTime()); + adjustedEndDate.setDate(adjustedEndDate.getDate() + 1); // Include end date + + return allVulns.stream() + .filter(v -> v.getOpened() != null) + .filter(v -> !v.getOpened().before(startDate) && v.getOpened().before(adjustedEndDate)) + .collect(Collectors.toList()); + } + + // If no date range, return vulnerabilities that have an opened date + return allVulns.stream() + .filter(v -> v.getOpened() != null) + .collect(Collectors.toList()); + } + + /** + * Returns the value actually populated on the entity for the given custom type, + * or an empty string. The custom type's configured default is never emitted: a + * field whose stored value still equals the type default is treated as not + * populated. This also covers unselected dropdowns, whose stored value is the + * full comma-separated option list held in the type's defaultValue. + */ + private String getPopulatedCustomFieldValue(List customFields, CustomType customType) { + if (customFields == null) { + return ""; + } + for (CustomField cf : customFields) { + if (cf.getType() != null && cf.getType().getId().equals(customType.getId())) { + String value = cf.getValue(); + if (value == null || value.isEmpty()) { + return ""; + } + String defaultValue = customType.getDefaultValue(); + if (defaultValue != null && value.equals(defaultValue)) { + return ""; + } + return value; + } + } + return ""; + } + + private String escapeCSV(String value) { + if (value == null) + return ""; + + // Escape quotes by doubling them and wrap in quotes if contains comma, quote, + // or newline + if (value.contains(",") || value.contains("\"") || value.contains("\n")) { + value = "\"" + value.replace("\"", "\"\"") + "\""; + } + return value; + } + + private void loadDropdownData() { + assessmentTypes = em.createQuery("from AssessmentType order by type").getResultList(); + teams = em.createQuery("from Teams order by TeamName").getResultList(); + riskLevels = em.createQuery("from RiskLevel order by riskId desc").getResultList(); + statuses = em.createQuery("from Status order by name").getResultList(); + // Get all users with assessor permission + assessors = em.createQuery("from User order by lname, fname", User.class) + .getResultList() + .stream() + .filter(a -> a.getPermissions() != null && a.getPermissions().isAssessor()) + .collect(Collectors.toList()); + // Get all campaigns + campaigns = em.createQuery("from Campaign order by name").getResultList(); + // Get all assessment custom types (type 0 = ASMT, fieldType < 3 excludes + // richtext fields that only exist in Enterprise versions) + assessmentCustomTypes = em.createQuery( + "from CustomType where type = 0 and fieldType < 3 and deleted = false order by key", + CustomType.class) + .getResultList(); + // Get all vulnerability custom types (type 1 = VULN, fieldType < 3 excludes forms) + vulnerabilityCustomTypes = em.createQuery( + "from CustomType where type = 1 and fieldType < 3 and deleted = false order by key", + CustomType.class) + .getResultList(); + } + + private List performAssessmentSearch() { + // Build MongoDB query for search mode (copied from ManagerDashboard) + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); + StringBuilder query = new StringBuilder("{"); + boolean hasConditions = false; + + // Build date range condition - include assessments that overlap with the search + // range + if (startDate != null && endDate != null) { + endDate.setDate(endDate.getDate() + 1); + query.append("$and: ["); + query.append(" { \"start\": {$lte: ISODate(\"").append(sdf.format(endDate)).append("\")}}, "); + query.append(" { $or: [ "); + query.append(" { \"completed\": { $exists: true, $gte: ISODate(\"") + .append(sdf.format(startDate)).append("\")}}"); + query.append(" ,"); + query.append(" { \"completed\": {$exists: false}},"); + query.append(" ]}"); + query.append("]"); + hasConditions = true; + } + + // Build assessment type condition + if (typeId != null && typeId > 0) { + if (hasConditions) + query.append(", "); + query.append("\"type_id\": ").append(typeId); + hasConditions = true; + } + + // Build team condition - need to check if any assessor belongs to the team + if (teamId != null && teamId > 0) { + // First, get the team entity + Teams team = em.find(Teams.class, teamId); + + if (team != null) { + // Use a native MongoDB query to find users with this team + String userQuery = "{\"team_id\": " + teamId + "}"; + List teamUsers = em.createNativeQuery(userQuery, User.class).getResultList(); + + if (!teamUsers.isEmpty()) { + if (hasConditions) + query.append(", "); + query.append("\"assessor\": {$in: ["); + boolean first = true; + for (User user : teamUsers) { + if (!first) + query.append(", "); + query.append(user.getId()); + first = false; + } + query.append("]}"); + hasConditions = true; + } + } + } + + // Build assessor condition + if (assessorId != null && assessorId > 0) { + if (hasConditions) + query.append(", "); + query.append("\"assessor\": ").append(assessorId); + hasConditions = true; + } + + // Build campaign condition + if (campaignId != null && campaignId > 0) { + if (hasConditions) + query.append(", "); + query.append("\"campaign_id\": ").append(campaignId); + hasConditions = true; + } + + query.append("}"); + // Execute the native MongoDB query + List mongoResults = em.createNativeQuery(query.toString(), Assessment.class).getResultList(); + + // Filter Results based on status + List searchResults; + if (status != null && !status.isEmpty() && !status.equals("0")) { + // Find the status name by ID + String statusName = null; + for (Status s : statuses) { + if (s.getId().toString().equals(status)) { + statusName = s.getName(); + break; + } + } + + if (statusName != null) { + final String finalStatusName = statusName; + searchResults = mongoResults.stream() + .filter(a -> finalStatusName.equals(a.getStatus())) + .collect(Collectors.toList()); + } else { + searchResults = mongoResults; + } + } else { + searchResults = mongoResults; + } + + // Sort by start date descending + searchResults.sort((a1, a2) -> { + if (a1.getStart() == null && a2.getStart() == null) + return 0; + if (a1.getStart() == null) + return 1; + if (a2.getStart() == null) + return -1; + return a2.getStart().compareTo(a1.getStart()); + }); + + return searchResults; + } + + private String getRiskLevelName(Long riskId) { + if (riskId == null) + return "Unassigned"; + + for (RiskLevel level : riskLevels) { + if (riskId.equals(Long.valueOf(level.getRiskId()))) { + return level.getRisk() != null ? level.getRisk() : "Unassigned"; + } + } + return "Unassigned"; + } + + // Getters and setters + public InputStream getInputStream() { + return inputStream; + } + + public void setInputStream(InputStream inputStream) { + this.inputStream = inputStream; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public Long getTypeId() { + return typeId; + } + + public void setTypeId(Long typeId) { + this.typeId = typeId; + } + + public Long getTeamId() { + return teamId; + } + + public void setTeamId(Long teamId) { + this.teamId = teamId; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public Long getAssessorId() { + return assessorId; + } + + public void setAssessorId(Long assessorId) { + this.assessorId = assessorId; + } + + public Long getCampaignId() { + return campaignId; + } + + public void setCampaignId(Long campaignId) { + this.campaignId = campaignId; + } + + public String getSearchAction() { + return searchAction; + } + + public void setSearchAction(String searchAction) { + this.searchAction = searchAction; + } +} diff --git a/test/com/fuse/actions/dashboard/ManagerDashboardCSVTest.java b/test/com/fuse/actions/dashboard/ManagerDashboardCSVTest.java new file mode 100644 index 00000000..a95d960c --- /dev/null +++ b/test/com/fuse/actions/dashboard/ManagerDashboardCSVTest.java @@ -0,0 +1,512 @@ +package com.fuse.actions.dashboard; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.text.SimpleDateFormat; +import java.util.*; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; + +import org.apache.struts2.junit.StrutsJUnit4TestCase; +import org.junit.Before; +import org.junit.Test; + +import com.fuse.dao.*; + +/** + * Tests for ManagerDashboardCSV action - Assessment CSV Export + * Tests authentication, authorization, and CSV content generation + */ +public class ManagerDashboardCSVTest extends StrutsJUnit4TestCase { + + private EntityManager mockEm; + private User managerUser; + private User assessorUser; + private User noPermUser; + + @Before + public void setUp() throws Exception { + super.setUp(); + setupTestUsers(); + } + + private void setupTestUsers() { + // Manager user (has manager permission) + managerUser = new User(); + managerUser.setId(1L); + managerUser.setUsername("manager"); + managerUser.setFname("Manager"); + managerUser.setLname("User"); + Permissions managerPerms = new Permissions(); + managerPerms.setManager(true); + managerPerms.setAssessor(true); + managerUser.setPermissions(managerPerms); + + Teams team = new Teams(); + team.setId(1L); + team.setTeamName("Security Team"); + managerUser.setTeam(team); + + // Assessor user (no manager permission) + assessorUser = new User(); + assessorUser.setId(2L); + assessorUser.setUsername("assessor"); + assessorUser.setFname("Assessor"); + assessorUser.setLname("User"); + Permissions assessorPerms = new Permissions(); + assessorPerms.setManager(false); + assessorPerms.setAssessor(true); + assessorUser.setPermissions(assessorPerms); + assessorUser.setTeam(team); + + // No permission user + noPermUser = new User(); + noPermUser.setId(3L); + noPermUser.setUsername("noperm"); + noPermUser.setFname("No"); + noPermUser.setLname("Permission"); + Permissions noPerms = new Permissions(); + noPerms.setManager(false); + noPerms.setAssessor(false); + noPermUser.setPermissions(noPerms); + } + + private Object getField(ManagerDashboardCSV action, String fieldName) throws Exception { + Class clazz = ManagerDashboardCSV.class; + while (clazz != null) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(action); + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(fieldName); + } + + private void setField(ManagerDashboardCSV action, String fieldName, Object value) throws Exception { + Class clazz = ManagerDashboardCSV.class; + while (clazz != null) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(action, value); + return; + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(fieldName); + } + + // --- Authentication & Authorization Tests --- + + @Test + public void testActionRequiresManagerRole() throws Exception { + ManagerDashboardCSV action = new ManagerDashboardCSV(); + + // Don't set manager role in ActionContext (simulates non-manager) + // The isAcmanager() method will return false + + String result = action.exportCSV(); + + assertEquals("Should return LOGIN for non-manager user", "login", result); + assertNull("Should not generate CSV for non-manager", action.getInputStream()); + } + + @Test + public void testActionAllowsManagerAccess() throws Exception { + ManagerDashboardCSV action = new ManagerDashboardCSV(); + + // Mock the EntityManager to avoid database calls + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + + // Set manager role in ActionContext + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + // Mock required data + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from AssessmentType order by type"); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Teams order by TeamName"); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from RiskLevel order by riskId desc"); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Status order by name"); + doReturn(mockTypedQuery(Arrays.asList(managerUser))) + .when(mockEm).createQuery("from User order by lname, fname", User.class); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Campaign order by name"); + doReturn(mockTypedQuery(new ArrayList())) + .when(mockEm).createQuery(contains("CustomType"), eq(CustomType.class)); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createNativeQuery(anyString(), eq(Assessment.class)); + + String result = action.exportCSV(); + + assertEquals("Should return SUCCESS for manager user", "success", result); + assertNotNull("Should generate CSV for manager", action.getInputStream()); + assertNotNull("Should set filename", action.getFilename()); + } + + @Test + public void testActionRequiresLogin() throws Exception { + ManagerDashboardCSV action = new ManagerDashboardCSV(); + + // No manager role set (not logged in or no permissions) + // ActionContext will not have isManager = true + + String result = action.exportCSV(); + + assertEquals("Should return LOGIN for unauthenticated user", "login", result); + } + + // --- CSV Content Tests --- + + @Test + public void testCSVHeaderGeneration() throws Exception { + ManagerDashboardCSV action = new ManagerDashboardCSV(); + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + // Set up risk levels + RiskLevel critical = new RiskLevel(); + critical.setRiskId(4); + critical.setRisk("Critical"); + + RiskLevel high = new RiskLevel(); + high.setRiskId(3); + high.setRisk("High"); + + List riskLevels = Arrays.asList(critical, high); + + // Set up custom types + CustomType customField1 = new CustomType(); + customField1.setId(1L); + customField1.setKey("Business Unit"); + customField1.setType(0); // ASMT type + customField1.setFieldType(0); + customField1.setDeleted(false); + + List customTypes = Arrays.asList(customField1); + + // Mock queries + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from AssessmentType order by type"); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Teams order by TeamName"); + doReturn(mockQuery(riskLevels)) + .when(mockEm).createQuery("from RiskLevel order by riskId desc"); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Status order by name"); + doReturn(mockTypedQuery(Arrays.asList(managerUser))) + .when(mockEm).createQuery("from User order by lname, fname", User.class); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Campaign order by name"); + doReturn(mockTypedQuery(customTypes)) + .when(mockEm).createQuery(contains("CustomType"), eq(CustomType.class)); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createNativeQuery(anyString(), eq(Assessment.class)); + + String result = action.exportCSV(); + + assertEquals("Should return SUCCESS", "success", result); + + // Read CSV content + BufferedReader reader = new BufferedReader( + new InputStreamReader(action.getInputStream())); + String header = reader.readLine(); + + // Verify header contains all expected columns + assertTrue("Header should contain AppId", header.contains("AppId")); + assertTrue("Header should contain Name", header.contains("Name")); + assertTrue("Header should contain Type", header.contains("Type")); + assertTrue("Header should contain Team", header.contains("Team")); + assertTrue("Header should contain Assessor", header.contains("Assessor")); + assertTrue("Header should contain Status", header.contains("Status")); + assertTrue("Header should contain custom field", header.contains("Business Unit")); + assertTrue("Header should contain Critical", header.contains("Critical")); + assertTrue("Header should contain High", header.contains("High")); + + reader.close(); + } + + @Test + public void testCSVDataRowGeneration() throws Exception { + ManagerDashboardCSV action = new ManagerDashboardCSV(); + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + // Create test assessment + Assessment testAsmt = createTestAssessment(); + + // Set up mock data + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from AssessmentType order by type"); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Teams order by TeamName"); + doReturn(mockQuery(createTestRiskLevels())) + .when(mockEm).createQuery("from RiskLevel order by riskId desc"); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Status order by name"); + doReturn(mockTypedQuery(Arrays.asList(managerUser))) + .when(mockEm).createQuery("from User order by lname, fname", User.class); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Campaign order by name"); + doReturn(mockTypedQuery(new ArrayList())) + .when(mockEm).createQuery(contains("CustomType"), eq(CustomType.class)); + doReturn(mockQuery(Arrays.asList(testAsmt))) + .when(mockEm).createNativeQuery(anyString(), eq(Assessment.class)); + doReturn(mockTypedQuery(new ArrayList())) + .when(mockEm).createQuery(contains("Vulnerability"), eq(Vulnerability.class)); + + String result = action.exportCSV(); + + assertEquals("Should return SUCCESS", "success", result); + + // Read CSV content + BufferedReader reader = new BufferedReader( + new InputStreamReader(action.getInputStream())); + String header = reader.readLine(); + String dataRow = reader.readLine(); + + assertNotNull("Should have data row", dataRow); + assertTrue("Data row should contain APP-001", dataRow.contains("APP-001")); + assertTrue("Data row should contain Test Assessment", dataRow.contains("Test Assessment")); + assertTrue("Data row should contain Web Application", dataRow.contains("Web Application")); + + reader.close(); + } + + @Test + public void testCSVEscapesSpecialCharacters() throws Exception { + ManagerDashboardCSV action = new ManagerDashboardCSV(); + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + // Create assessment with special characters + Assessment testAsmt = createTestAssessment(); + testAsmt.setName("Test, \"Assessment\" with\nSpecial Characters"); + testAsmt.setAppId("APP,001"); + + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from AssessmentType order by type"); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Teams order by TeamName"); + doReturn(mockQuery(createTestRiskLevels())) + .when(mockEm).createQuery("from RiskLevel order by riskId desc"); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Status order by name"); + doReturn(mockTypedQuery(Arrays.asList(managerUser))) + .when(mockEm).createQuery("from User order by lname, fname", User.class); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Campaign order by name"); + doReturn(mockTypedQuery(new ArrayList())) + .when(mockEm).createQuery(contains("CustomType"), eq(CustomType.class)); + doReturn(mockQuery(Arrays.asList(testAsmt))) + .when(mockEm).createNativeQuery(anyString(), eq(Assessment.class)); + doReturn(mockTypedQuery(new ArrayList())) + .when(mockEm).createQuery(contains("Vulnerability"), eq(Vulnerability.class)); + + String result = action.exportCSV(); + assertEquals("Should return SUCCESS", "success", result); + + BufferedReader reader = new BufferedReader( + new InputStreamReader(action.getInputStream())); + reader.readLine(); // Skip header + String dataRow = reader.readLine(); + + // Verify CSV escaping (quotes should be escaped, values with special chars wrapped in quotes) + assertTrue("Should escape commas", dataRow.contains("\"APP,001\"")); + assertTrue("Should escape quotes", dataRow.contains("\"\"Assessment\"\"")); + + reader.close(); + } + + @Test + public void testCustomFieldDefaultValuesAreNotShown() throws Exception { + ManagerDashboardCSV action = new ManagerDashboardCSV(); + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + // Dropdown (LIST) custom type whose defaultValue is the full option list. + CustomType listType = new CustomType(); + listType.setId(20L); + listType.setKey("Environment"); + listType.setType(0); + listType.setFieldType(2); + listType.setDefaultValue("Dev,QA,Prod"); + listType.setDeleted(false); + + Assessment testAsmt = createTestAssessment(); + // Unselected dropdown: stored value still equals the default option list. + CustomField unselected = new CustomField(); + unselected.setType(listType); + unselected.setValue("Dev,QA,Prod"); + testAsmt.setCustomFields(Arrays.asList(unselected)); + + mockAllRequiredQueries(Arrays.asList(testAsmt)); + doReturn(mockTypedQuery(Arrays.asList(listType))) + .when(mockEm).createQuery(contains("type = 0"), eq(CustomType.class)); + + String result = action.exportCSV(); + assertEquals("Should return SUCCESS", "success", result); + + BufferedReader reader = new BufferedReader( + new InputStreamReader(action.getInputStream())); + String content = ""; + String line; + while ((line = reader.readLine()) != null) { + content += line + "\n"; + } + + assertFalse("Default dropdown option list should not be shown", + content.contains("Dev,QA,Prod")); + + reader.close(); + } + + @Test + public void testFilenameFormat() throws Exception { + ManagerDashboardCSV action = new ManagerDashboardCSV(); + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + mockAllRequiredQueries(new ArrayList()); + + action.exportCSV(); + + String filename = action.getFilename(); + assertNotNull("Filename should not be null", filename); + assertTrue("Filename should start with manager_dashboard_assessments_", + filename.startsWith("manager_dashboard_assessments_")); + assertTrue("Filename should end with .csv", filename.endsWith(".csv")); + assertTrue("Filename should contain timestamp", + filename.matches("manager_dashboard_assessments_\\d{8}_\\d{6}\\.csv")); + } + + // --- Date Filtering Tests --- + + @Test + public void testDateRangeFiltering() throws Exception { + ManagerDashboardCSV action = new ManagerDashboardCSV(); + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + // Set date range + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + Date startDate = sdf.parse("2024-01-01"); + Date endDate = sdf.parse("2024-12-31"); + + setField(action, "startDate", startDate); + setField(action, "endDate", endDate); + + mockAllRequiredQueries(new ArrayList()); + + String result = action.exportCSV(); + + assertEquals("Should return SUCCESS", "success", result); + + // Verify the MongoDB query was called with date parameters + verify(mockEm).createNativeQuery(contains("$and"), eq(Assessment.class)); + verify(mockEm).createNativeQuery(contains("start"), eq(Assessment.class)); + } + + // --- Helper Methods --- + + private Assessment createTestAssessment() { + Assessment asmt = new Assessment(); + asmt.setId(1L); + asmt.setName("Test Assessment"); + asmt.setAppId("APP-001"); + asmt.setStart(new Date()); + asmt.setEnd(new Date()); + asmt.setStatus("In Progress"); + + AssessmentType type = new AssessmentType(); + type.setId(1L); + type.setType("Web Application"); + asmt.setType(type); + + asmt.setAssessor(Arrays.asList(managerUser)); + asmt.setCustomFields(new ArrayList<>()); + + return asmt; + } + + private List createTestRiskLevels() { + List levels = new ArrayList<>(); + + RiskLevel critical = new RiskLevel(); + critical.setRiskId(4); + critical.setRisk("Critical"); + levels.add(critical); + + RiskLevel high = new RiskLevel(); + high.setRiskId(3); + high.setRisk("High"); + levels.add(high); + + RiskLevel medium = new RiskLevel(); + medium.setRiskId(2); + medium.setRisk("Medium"); + levels.add(medium); + + RiskLevel low = new RiskLevel(); + low.setRiskId(1); + low.setRisk("Low"); + levels.add(low); + + return levels; + } + + private void mockAllRequiredQueries(List assessments) { + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from AssessmentType order by type"); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Teams order by TeamName"); + doReturn(mockQuery(createTestRiskLevels())) + .when(mockEm).createQuery("from RiskLevel order by riskId desc"); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Status order by name"); + doReturn(mockTypedQuery(Arrays.asList(managerUser))) + .when(mockEm).createQuery("from User order by lname, fname", User.class); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Campaign order by name"); + doReturn(mockTypedQuery(new ArrayList())) + .when(mockEm).createQuery(contains("CustomType"), eq(CustomType.class)); + doReturn(mockQuery(assessments)) + .when(mockEm).createNativeQuery(anyString(), eq(Assessment.class)); + doReturn(mockTypedQuery(new ArrayList())) + .when(mockEm).createQuery(contains("Vulnerability"), eq(Vulnerability.class)); + } + + @SuppressWarnings("unchecked") + private TypedQuery mockTypedQuery(List results) { + TypedQuery query = mock(TypedQuery.class); + doReturn(results).when(query).getResultList(); + doReturn(query).when(query).setParameter(anyString(), any()); + return query; + } + + @SuppressWarnings("unchecked") + private javax.persistence.Query mockQuery(List results) { + javax.persistence.Query query = mock(javax.persistence.Query.class); + doReturn(results).when(query).getResultList(); + doReturn(query).when(query).setParameter(anyString(), any()); + return query; + } +} diff --git a/test/com/fuse/actions/dashboard/ManagerDashboardVulnerabilitiesCSVTest.java b/test/com/fuse/actions/dashboard/ManagerDashboardVulnerabilitiesCSVTest.java new file mode 100644 index 00000000..ecd88f14 --- /dev/null +++ b/test/com/fuse/actions/dashboard/ManagerDashboardVulnerabilitiesCSVTest.java @@ -0,0 +1,800 @@ +package com.fuse.actions.dashboard; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.text.SimpleDateFormat; +import java.util.*; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; + +import org.apache.struts2.junit.StrutsJUnit4TestCase; +import org.junit.Before; +import org.junit.Test; + +import com.fuse.dao.*; + +/** + * Tests for ManagerDashboardVulnerabilitiesCSV action - Vulnerability CSV Export + * Tests authentication, authorization, date filtering, and CSV content generation + */ +public class ManagerDashboardVulnerabilitiesCSVTest extends StrutsJUnit4TestCase { + + private EntityManager mockEm; + private User managerUser; + private User assessorUser; + private User noPermUser; + + @Before + public void setUp() throws Exception { + super.setUp(); + setupTestUsers(); + } + + private void setupTestUsers() { + // Manager user (has manager permission) + managerUser = new User(); + managerUser.setId(1L); + managerUser.setUsername("manager"); + managerUser.setFname("Manager"); + managerUser.setLname("User"); + Permissions managerPerms = new Permissions(); + managerPerms.setManager(true); + managerPerms.setAssessor(true); + managerUser.setPermissions(managerPerms); + + Teams team = new Teams(); + team.setId(1L); + team.setTeamName("Security Team"); + managerUser.setTeam(team); + + // Assessor user (no manager permission) + assessorUser = new User(); + assessorUser.setId(2L); + assessorUser.setUsername("assessor"); + assessorUser.setFname("Assessor"); + assessorUser.setLname("User"); + Permissions assessorPerms = new Permissions(); + assessorPerms.setManager(false); + assessorPerms.setAssessor(true); + assessorUser.setPermissions(assessorPerms); + assessorUser.setTeam(team); + + // No permission user + noPermUser = new User(); + noPermUser.setId(3L); + noPermUser.setUsername("noperm"); + noPermUser.setFname("No"); + noPermUser.setLname("Permission"); + Permissions noPerms = new Permissions(); + noPerms.setManager(false); + noPerms.setAssessor(false); + noPermUser.setPermissions(noPerms); + } + + private Object getField(ManagerDashboardVulnerabilitiesCSV action, String fieldName) throws Exception { + Class clazz = ManagerDashboardVulnerabilitiesCSV.class; + while (clazz != null) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(action); + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(fieldName); + } + + private void setField(ManagerDashboardVulnerabilitiesCSV action, String fieldName, Object value) throws Exception { + Class clazz = ManagerDashboardVulnerabilitiesCSV.class; + while (clazz != null) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(action, value); + return; + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(fieldName); + } + + // --- Authentication & Authorization Tests --- + + @Test + public void testActionRequiresManagerRole() throws Exception { + ManagerDashboardVulnerabilitiesCSV action = new ManagerDashboardVulnerabilitiesCSV(); + + // Don't set manager role in ActionContext (simulates non-manager) + + String result = action.exportCSV(); + + assertEquals("Should return LOGIN for non-manager user", "login", result); + assertNull("Should not generate CSV for non-manager", action.getInputStream()); + } + + @Test + public void testActionAllowsManagerAccess() throws Exception { + ManagerDashboardVulnerabilitiesCSV action = new ManagerDashboardVulnerabilitiesCSV(); + + // Mock the EntityManager to avoid database calls + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + mockAllRequiredQueries(new ArrayList()); + + String result = action.exportCSV(); + + assertEquals("Should return SUCCESS for manager user", "success", result); + assertNotNull("Should generate CSV for manager", action.getInputStream()); + assertNotNull("Should set filename", action.getFilename()); + } + + @Test + public void testActionRequiresLogin() throws Exception { + ManagerDashboardVulnerabilitiesCSV action = new ManagerDashboardVulnerabilitiesCSV(); + + // No manager role set (not logged in or no permissions) + + String result = action.exportCSV(); + + assertEquals("Should return LOGIN for unauthenticated user", "login", result); + } + + @Test + public void testNonManagerAssessorDenied() throws Exception { + ManagerDashboardVulnerabilitiesCSV action = new ManagerDashboardVulnerabilitiesCSV(); + + // Assessor without manager permission (no isManager in ActionContext) + + String result = action.exportCSV(); + + assertEquals("Assessor without manager permission should be denied", "login", result); + assertNull("Should not generate CSV", action.getInputStream()); + } + + @Test + public void testUserWithNoPermissionsDenied() throws Exception { + ManagerDashboardVulnerabilitiesCSV action = new ManagerDashboardVulnerabilitiesCSV(); + + // User with no permissions (no isManager in ActionContext) + + String result = action.exportCSV(); + + assertEquals("User with no permissions should be denied", "login", result); + assertNull("Should not generate CSV", action.getInputStream()); + } + + // --- CSV Content Tests --- + + @Test + public void testCSVHeaderGeneration() throws Exception { + ManagerDashboardVulnerabilitiesCSV action = new ManagerDashboardVulnerabilitiesCSV(); + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + // Set up vulnerability custom types + CustomType customField1 = new CustomType(); + customField1.setId(1L); + customField1.setKey("Affected Component"); + customField1.setType(1); // VULN type + customField1.setFieldType(0); + customField1.setDeleted(false); + + List customTypes = Arrays.asList(customField1); + + mockAllRequiredQueriesWithCustomTypes(new ArrayList(), customTypes); + + String result = action.exportCSV(); + + assertEquals("Should return SUCCESS", "success", result); + + // Read CSV content + BufferedReader reader = new BufferedReader( + new InputStreamReader(action.getInputStream())); + String header = reader.readLine(); + + // Verify header contains all expected columns + assertTrue("Header should contain Vulnerability ID", header.contains("Vulnerability ID")); + assertTrue("Header should contain Vulnerability Name", header.contains("Vulnerability Name")); + assertTrue("Header should contain Assessment Name", header.contains("Assessment Name")); + assertTrue("Header should contain Assessment AppId", header.contains("Assessment AppId")); + assertTrue("Header should contain Severity", header.contains("Severity")); + assertTrue("Header should contain CVSS Score", header.contains("CVSS Score")); + assertTrue("Header should contain Category", header.contains("Category")); + assertTrue("Header should contain Opened Date", header.contains("Opened Date")); + assertTrue("Header should contain Closed Date", header.contains("Closed Date")); + assertTrue("Header should contain Status", header.contains("Status")); + assertTrue("Header should contain Tracking ID", header.contains("Tracking ID")); + assertTrue("Header should contain custom field", header.contains("Affected Component")); + + reader.close(); + } + + @Test + public void testAssessmentInfoColumnsIncluded() throws Exception { + ManagerDashboardVulnerabilitiesCSV action = new ManagerDashboardVulnerabilitiesCSV(); + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + // Assessment custom field (type 0 = ASMT, fieldType 0 = STRING) + CustomType asmtField = new CustomType(); + asmtField.setId(10L); + asmtField.setKey("Business Unit"); + asmtField.setType(0); + asmtField.setFieldType(0); + asmtField.setDeleted(false); + + Assessment testAsmt = createTestAssessment(); + CustomField cf = new CustomField(); + cf.setType(asmtField); + cf.setValue("Finance"); + testAsmt.setCustomFields(Arrays.asList(cf)); + + mockAllRequiredQueries(Arrays.asList(testAsmt)); + // Assessment custom types (type = 0) return the field; vuln types (type = 1) empty + doReturn(mockTypedQuery(Arrays.asList(asmtField))) + .when(mockEm).createQuery(contains("type = 0"), eq(CustomType.class)); + doReturn(mockTypedQuery(new ArrayList())) + .when(mockEm).createQuery(contains("type = 1"), eq(CustomType.class)); + + List vulns = createTestVulnerabilities(); + TypedQuery vulnQuery = mock(TypedQuery.class); + doReturn(vulnQuery).when(vulnQuery).setParameter(eq("aid"), any()); + doReturn(vulns).when(vulnQuery).getResultList(); + doReturn(vulnQuery).when(mockEm).createQuery(contains("Vulnerability"), eq(Vulnerability.class)); + + String result = action.exportCSV(); + assertEquals("Should return SUCCESS", "success", result); + + BufferedReader reader = new BufferedReader( + new InputStreamReader(action.getInputStream())); + String header = reader.readLine(); + String dataRow = reader.readLine(); + + // Header includes the assessment id and the assessment custom field + assertTrue("Header should contain Assessment ID", header.contains("Assessment ID")); + assertTrue("Header should contain Assessment Name", header.contains("Assessment Name")); + assertTrue("Header should contain Assessment AppId", header.contains("Assessment AppId")); + assertTrue("Header should contain assessment custom field", header.contains("Business Unit")); + + // Data row includes the assessment id, app id, name, and custom field value + assertNotNull("Should have data row", dataRow); + assertTrue("Data row should contain assessment id", dataRow.contains("1")); + assertTrue("Data row should contain assessment name", dataRow.contains("Test Assessment")); + assertTrue("Data row should contain app id", dataRow.contains("APP-001")); + assertTrue("Data row should contain custom field value", dataRow.contains("Finance")); + + reader.close(); + } + + @Test + public void testCustomFieldDefaultValuesAreNotShown() throws Exception { + ManagerDashboardVulnerabilitiesCSV action = new ManagerDashboardVulnerabilitiesCSV(); + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + // Assessment dropdown (LIST) custom type whose defaultValue is the full + // option list; an unselected field stores that whole list as its value. + CustomType asmtList = new CustomType(); + asmtList.setId(20L); + asmtList.setKey("Environment"); + asmtList.setType(0); + asmtList.setFieldType(2); + asmtList.setDefaultValue("Dev,QA,Prod"); + asmtList.setDeleted(false); + + // Vulnerability custom type with a plain string default. + CustomType vulnField = new CustomType(); + vulnField.setId(21L); + vulnField.setKey("Remediation Owner"); + vulnField.setType(1); + vulnField.setFieldType(0); + vulnField.setDefaultValue("PlaceholderOwner"); + vulnField.setDeleted(false); + + Assessment testAsmt = createTestAssessment(); + // Assessment field still holds the default (unselected dropdown). + CustomField asmtCf = new CustomField(); + asmtCf.setType(asmtList); + asmtCf.setValue("Dev,QA,Prod"); + testAsmt.setCustomFields(Arrays.asList(asmtCf)); + + // Vulnerability: one field left at its default, one genuinely populated. + Vulnerability vuln = new Vulnerability(); + vuln.setId(1L); + vuln.setName("SQL Injection"); + vuln.setOpened(new Date()); + vuln.setTracking("VID-001"); + CustomField vulnCfDefault = new CustomField(); + vulnCfDefault.setType(vulnField); + vulnCfDefault.setValue("PlaceholderOwner"); + vuln.setCustomFields(Arrays.asList(vulnCfDefault)); + + mockAllRequiredQueries(Arrays.asList(testAsmt)); + doReturn(mockTypedQuery(Arrays.asList(asmtList))) + .when(mockEm).createQuery(contains("type = 0"), eq(CustomType.class)); + doReturn(mockTypedQuery(Arrays.asList(vulnField))) + .when(mockEm).createQuery(contains("type = 1"), eq(CustomType.class)); + + TypedQuery vulnQuery = mock(TypedQuery.class); + doReturn(vulnQuery).when(vulnQuery).setParameter(eq("aid"), any()); + doReturn(Arrays.asList(vuln)).when(vulnQuery).getResultList(); + doReturn(vulnQuery).when(mockEm).createQuery(contains("Vulnerability"), eq(Vulnerability.class)); + + String result = action.exportCSV(); + assertEquals("Should return SUCCESS", "success", result); + + BufferedReader reader = new BufferedReader( + new InputStreamReader(action.getInputStream())); + String content = ""; + String line; + while ((line = reader.readLine()) != null) { + content += line + "\n"; + } + + // The dropdown's default option list must not leak into the output. + assertFalse("Default dropdown option list should not be shown", + content.contains("Dev,QA,Prod")); + assertFalse("Default dropdown option list should not be shown (quoted)", + content.contains("\"Dev,QA,Prod\"")); + // The string field still at its default must not be shown either. + assertFalse("Default custom field value should not be shown", + content.contains("PlaceholderOwner")); + + reader.close(); + } + + @Test + public void testPopulatedCustomFieldValuesAreShown() throws Exception { + ManagerDashboardVulnerabilitiesCSV action = new ManagerDashboardVulnerabilitiesCSV(); + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + // Dropdown custom type with a real selection that differs from the default. + CustomType asmtList = new CustomType(); + asmtList.setId(20L); + asmtList.setKey("Environment"); + asmtList.setType(0); + asmtList.setFieldType(2); + asmtList.setDefaultValue("Dev,QA,Prod"); + asmtList.setDeleted(false); + + Assessment testAsmt = createTestAssessment(); + CustomField asmtCf = new CustomField(); + asmtCf.setType(asmtList); + asmtCf.setValue("Prod"); // user selected a single option + testAsmt.setCustomFields(Arrays.asList(asmtCf)); + + mockAllRequiredQueries(Arrays.asList(testAsmt)); + doReturn(mockTypedQuery(Arrays.asList(asmtList))) + .when(mockEm).createQuery(contains("type = 0"), eq(CustomType.class)); + doReturn(mockTypedQuery(new ArrayList())) + .when(mockEm).createQuery(contains("type = 1"), eq(CustomType.class)); + + List vulns = createTestVulnerabilities(); + TypedQuery vulnQuery = mock(TypedQuery.class); + doReturn(vulnQuery).when(vulnQuery).setParameter(eq("aid"), any()); + doReturn(vulns).when(vulnQuery).getResultList(); + doReturn(vulnQuery).when(mockEm).createQuery(contains("Vulnerability"), eq(Vulnerability.class)); + + String result = action.exportCSV(); + assertEquals("Should return SUCCESS", "success", result); + + BufferedReader reader = new BufferedReader( + new InputStreamReader(action.getInputStream())); + String header = reader.readLine(); + String dataRow = reader.readLine(); + + assertTrue("Selected dropdown value should be shown", dataRow.contains("Prod")); + assertFalse("Full option list should not be shown", dataRow.contains("Dev,QA,Prod")); + + reader.close(); + } + + @Test + public void testAssessmentCustomTypeQueryExcludesRichtext() throws Exception { + ManagerDashboardVulnerabilitiesCSV action = new ManagerDashboardVulnerabilitiesCSV(); + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + mockAllRequiredQueries(new ArrayList()); + + String result = action.exportCSV(); + assertEquals("Should return SUCCESS", "success", result); + + // The assessment custom type lookup must filter out richtext fields (fieldType 3), + // which only exist in Enterprise versions, via the "fieldType < 3" predicate. + verify(mockEm).createQuery(contains("type = 0 and fieldType < 3"), eq(CustomType.class)); + } + + @Test + public void testCSVDataRowGeneration() throws Exception { + ManagerDashboardVulnerabilitiesCSV action = new ManagerDashboardVulnerabilitiesCSV(); + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + // Create test assessment with vulnerabilities + Assessment testAsmt = createTestAssessment(); + List vulns = createTestVulnerabilities(); + + mockAllRequiredQueries(Arrays.asList(testAsmt)); + + // Mock vulnerability query + TypedQuery vulnQuery = mock(TypedQuery.class); + when(vulnQuery.setParameter(eq("aid"), any())).thenReturn(vulnQuery); + when(vulnQuery.getResultList()).thenReturn(vulns); + when(mockEm.createQuery(contains("Vulnerability"), eq(Vulnerability.class))) + .thenReturn(vulnQuery); + + String result = action.exportCSV(); + + assertEquals("Should return SUCCESS", "success", result); + + // Read CSV content + BufferedReader reader = new BufferedReader( + new InputStreamReader(action.getInputStream())); + String header = reader.readLine(); + String dataRow = reader.readLine(); + + assertNotNull("Should have data row", dataRow); + assertTrue("Data row should contain vulnerability name", dataRow.contains("SQL Injection")); + assertTrue("Data row should contain assessment name", dataRow.contains("Test Assessment")); + assertTrue("Data row should contain APP-001", dataRow.contains("APP-001")); + assertTrue("Data row should contain Critical", dataRow.contains("Critical")); + assertTrue("Data row should contain tracking ID", dataRow.contains("VID-")); + + reader.close(); + } + + @Test + public void testVulnerabilityDateFiltering() throws Exception { + ManagerDashboardVulnerabilitiesCSV action = new ManagerDashboardVulnerabilitiesCSV(); + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + // Set date range + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + Date startDate = sdf.parse("2024-01-01"); + Date endDate = sdf.parse("2024-12-31"); + + setField(action, "startDate", startDate); + setField(action, "endDate", endDate); + + // Create vulnerabilities with different opened dates + List vulns = new ArrayList<>(); + + // Vulnerability 1: Opened in date range (should be included) + Vulnerability vuln1 = new Vulnerability(); + vuln1.setId(1L); + vuln1.setName("SQL Injection"); + vuln1.setOpened(sdf.parse("2024-06-15")); + vuln1.setOverall(4L); + vuln1.setTracking("VID-001"); + vulns.add(vuln1); + + // Vulnerability 2: Opened before date range (should be excluded) + Vulnerability vuln2 = new Vulnerability(); + vuln2.setId(2L); + vuln2.setName("XSS"); + vuln2.setOpened(sdf.parse("2023-12-15")); + vuln2.setOverall(3L); + vuln2.setTracking("VID-002"); + vulns.add(vuln2); + + // Vulnerability 3: Opened after date range (should be excluded) + Vulnerability vuln3 = new Vulnerability(); + vuln3.setId(3L); + vuln3.setName("CSRF"); + vuln3.setOpened(sdf.parse("2025-01-15")); + vuln3.setOverall(2L); + vuln3.setTracking("VID-003"); + vulns.add(vuln3); + + Assessment testAsmt = createTestAssessment(); + + mockAllRequiredQueries(Arrays.asList(testAsmt)); + + TypedQuery vulnQuery = mock(TypedQuery.class); + when(vulnQuery.setParameter(eq("aid"), any())).thenReturn(vulnQuery); + when(vulnQuery.getResultList()).thenReturn(vulns); + when(mockEm.createQuery(contains("Vulnerability"), eq(Vulnerability.class))) + .thenReturn(vulnQuery); + + String result = action.exportCSV(); + assertEquals("Should return SUCCESS", "success", result); + + BufferedReader reader = new BufferedReader( + new InputStreamReader(action.getInputStream())); + String content = ""; + String line; + while ((line = reader.readLine()) != null) { + content += line + "\n"; + } + + // Only vuln1 should be in the export + assertTrue("Should contain SQL Injection (in date range)", content.contains("SQL Injection")); + assertFalse("Should not contain XSS (before date range)", content.contains("XSS")); + assertFalse("Should not contain CSRF (after date range)", content.contains("CSRF")); + + reader.close(); + } + + @Test + public void testVulnerabilityWithoutOpenedDateExcluded() throws Exception { + ManagerDashboardVulnerabilitiesCSV action = new ManagerDashboardVulnerabilitiesCSV(); + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + List vulns = new ArrayList<>(); + + // Vulnerability without opened date + Vulnerability vuln1 = new Vulnerability(); + vuln1.setId(1L); + vuln1.setName("No Opened Date"); + vuln1.setOpened(null); // No opened date + vuln1.setOverall(4L); + vuln1.setTracking("VID-001"); + vulns.add(vuln1); + + // Vulnerability with opened date + Vulnerability vuln2 = new Vulnerability(); + vuln2.setId(2L); + vuln2.setName("Has Opened Date"); + vuln2.setOpened(new Date()); + vuln2.setOverall(3L); + vuln2.setTracking("VID-002"); + vulns.add(vuln2); + + Assessment testAsmt = createTestAssessment(); + + mockAllRequiredQueries(Arrays.asList(testAsmt)); + + TypedQuery vulnQuery = mock(TypedQuery.class); + when(vulnQuery.setParameter(eq("aid"), any())).thenReturn(vulnQuery); + when(vulnQuery.getResultList()).thenReturn(vulns); + when(mockEm.createQuery(contains("Vulnerability"), eq(Vulnerability.class))) + .thenReturn(vulnQuery); + + String result = action.exportCSV(); + assertEquals("Should return SUCCESS", "success", result); + + BufferedReader reader = new BufferedReader( + new InputStreamReader(action.getInputStream())); + String content = ""; + String line; + while ((line = reader.readLine()) != null) { + content += line + "\n"; + } + + assertFalse("Should not contain vulnerability without opened date", + content.contains("No Opened Date")); + assertTrue("Should contain vulnerability with opened date", + content.contains("Has Opened Date")); + + reader.close(); + } + + @Test + public void testFilenameFormat() throws Exception { + ManagerDashboardVulnerabilitiesCSV action = new ManagerDashboardVulnerabilitiesCSV(); + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + mockAllRequiredQueries(new ArrayList()); + + action.exportCSV(); + + String filename = action.getFilename(); + assertNotNull("Filename should not be null", filename); + assertTrue("Filename should start with manager_dashboard_vulnerabilities_", + filename.startsWith("manager_dashboard_vulnerabilities_")); + assertTrue("Filename should end with .csv", filename.endsWith(".csv")); + assertTrue("Filename should contain timestamp", + filename.matches("manager_dashboard_vulnerabilities_\\d{8}_\\d{6}\\.csv")); + } + + @Test + public void testCSVEscapesSpecialCharacters() throws Exception { + ManagerDashboardVulnerabilitiesCSV action = new ManagerDashboardVulnerabilitiesCSV(); + mockEm = mock(EntityManager.class); + setField(action, "em", mockEm); + com.opensymphony.xwork2.ActionContext.getContext().put("isManager", true); + + Assessment testAsmt = createTestAssessment(); + + // Vulnerability with special characters + List vulns = new ArrayList<>(); + Vulnerability vuln = new Vulnerability(); + vuln.setId(1L); + vuln.setName("SQL Injection, \"Severe\" with\nNewline"); + vuln.setOpened(new Date()); + vuln.setOverall(4L); + vuln.setTracking("VID,001"); + + Category cat = new Category(); + cat.setName("Injection, \"Critical\""); + vuln.setCategory(cat); + + vulns.add(vuln); + + mockAllRequiredQueries(Arrays.asList(testAsmt)); + + TypedQuery vulnQuery = mock(TypedQuery.class); + when(vulnQuery.setParameter(eq("aid"), any())).thenReturn(vulnQuery); + when(vulnQuery.getResultList()).thenReturn(vulns); + when(mockEm.createQuery(contains("Vulnerability"), eq(Vulnerability.class))) + .thenReturn(vulnQuery); + + String result = action.exportCSV(); + assertEquals("Should return SUCCESS", "success", result); + + // The vulnerability name contains an embedded newline, which is correctly + // quoted in the CSV but spans two physical lines. Read the whole payload + // rather than a single line so the escaped fields are all visible. + BufferedReader reader = new BufferedReader( + new InputStreamReader(action.getInputStream())); + StringBuilder contentBuilder = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + contentBuilder.append(line).append("\n"); + } + String content = contentBuilder.toString(); + + // Verify CSV escaping + assertTrue("Should escape commas", content.contains("\"VID,001\"")); + assertTrue("Should escape quotes in name", content.contains("\"\"Severe\"\"")); + assertTrue("Should escape category with comma", content.contains("\"Injection, \"\"Critical\"\"\"")); + + reader.close(); + } + + // --- Helper Methods --- + + private Assessment createTestAssessment() { + Assessment asmt = new Assessment(); + asmt.setId(1L); + asmt.setName("Test Assessment"); + asmt.setAppId("APP-001"); + asmt.setStart(new Date()); + asmt.setEnd(new Date()); + asmt.setStatus("In Progress"); + + AssessmentType type = new AssessmentType(); + type.setId(1L); + type.setType("Web Application"); + asmt.setType(type); + + asmt.setAssessor(Arrays.asList(managerUser)); + asmt.setCustomFields(new ArrayList<>()); + + return asmt; + } + + private List createTestVulnerabilities() { + List vulns = new ArrayList<>(); + + Vulnerability vuln1 = new Vulnerability(); + vuln1.setId(1L); + vuln1.setName("SQL Injection"); + vuln1.setOpened(new Date()); + vuln1.setOverall(4L); // Critical + vuln1.setCvssScore("9.8"); + vuln1.setTracking("VID-001"); + + Category cat1 = new Category(); + cat1.setName("Injection"); + vuln1.setCategory(cat1); + vuln1.setCustomFields(new ArrayList<>()); + + vulns.add(vuln1); + + Vulnerability vuln2 = new Vulnerability(); + vuln2.setId(2L); + vuln2.setName("XSS"); + vuln2.setOpened(new Date()); + vuln2.setClosed(new Date()); + vuln2.setOverall(3L); // High + vuln2.setCvssScore("7.5"); + vuln2.setTracking("VID-002"); + + Category cat2 = new Category(); + cat2.setName("XSS"); + vuln2.setCategory(cat2); + vuln2.setCustomFields(new ArrayList<>()); + + vulns.add(vuln2); + + return vulns; + } + + private List createTestRiskLevels() { + // Risk levels must cover riskId 1-5: Vulnerability.setCvssScore() maps the + // CVSS score onto overall (>=9.0 -> 5, >=7.0 -> 4, >=4.0 -> 3, >0 -> 2, else 1), + // so a 9.8 CVSS vulnerability resolves to riskId 5. + List levels = new ArrayList<>(); + + RiskLevel critical = new RiskLevel(); + critical.setRiskId(5); + critical.setRisk("Critical"); + levels.add(critical); + + RiskLevel high = new RiskLevel(); + high.setRiskId(4); + high.setRisk("High"); + levels.add(high); + + RiskLevel medium = new RiskLevel(); + medium.setRiskId(3); + medium.setRisk("Medium"); + levels.add(medium); + + RiskLevel low = new RiskLevel(); + low.setRiskId(2); + low.setRisk("Low"); + levels.add(low); + + RiskLevel info = new RiskLevel(); + info.setRiskId(1); + info.setRisk("Informational"); + levels.add(info); + + return levels; + } + + private void mockAllRequiredQueries(List assessments) { + mockAllRequiredQueriesWithCustomTypes(assessments, new ArrayList()); + } + + private void mockAllRequiredQueriesWithCustomTypes(List assessments, + List customTypes) { + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from AssessmentType order by type"); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Teams order by TeamName"); + doReturn(mockQuery(createTestRiskLevels())) + .when(mockEm).createQuery("from RiskLevel order by riskId desc"); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Status order by name"); + doReturn(mockTypedQuery(Arrays.asList(managerUser))) + .when(mockEm).createQuery("from User order by lname, fname", User.class); + doReturn(mockQuery(new ArrayList())) + .when(mockEm).createQuery("from Campaign order by name"); + doReturn(mockTypedQuery(customTypes)) + .when(mockEm).createQuery(contains("CustomType"), eq(CustomType.class)); + doReturn(mockQuery(assessments)) + .when(mockEm).createNativeQuery(anyString(), eq(Assessment.class)); + } + + @SuppressWarnings("unchecked") + private TypedQuery mockTypedQuery(List results) { + TypedQuery query = mock(TypedQuery.class); + doReturn(results).when(query).getResultList(); + doReturn(query).when(query).setParameter(anyString(), any()); + return query; + } + + @SuppressWarnings("unchecked") + private javax.persistence.Query mockQuery(List results) { + javax.persistence.Query query = mock(javax.persistence.Query.class); + doReturn(results).when(query).getResultList(); + doReturn(query).when(query).setParameter(anyString(), any()); + return query; + } +} From 90f0fbf9a8babbe1644b3cbaf73d7e2c0da22c5b Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 13 Jun 2026 15:20:02 -0500 Subject: [PATCH 6/8] Add vulnerabilities tab with detail panel to Manager Dashboard Split the dashboard's bottom table into Assessments and Vulnerabilities tabs. The vulnerabilities tab lists every vulnerability opened within the selected date range, matching the vulnerability CSV export. Clicking a vulnerability row opens the same slide-out detail panel used on Assessment > History, served by a new manager-only ManagerDashboardVulnDetail endpoint that renders vulnDetailPanel.jsp. --- .../jsp/dashboard/ManagerDashboard.jsp | 103 +++++++++++- WebContent/dist/js/manager_dashboard.js | 2 +- WebContent/src/dashboard/manager_dashboard.js | 64 +++++++- .../actions/dashboard/ManagerDashboard.java | 153 +++++++++++++++++- 4 files changed, 312 insertions(+), 10 deletions(-) diff --git a/WebContent/WEB-INF/jsp/dashboard/ManagerDashboard.jsp b/WebContent/WEB-INF/jsp/dashboard/ManagerDashboard.jsp index e8a87a94..6b900e52 100644 --- a/WebContent/WEB-INF/jsp/dashboard/ManagerDashboard.jsp +++ b/WebContent/WEB-INF/jsp/dashboard/ManagerDashboard.jsp @@ -801,13 +801,19 @@ - + - - + @@ -874,6 +914,55 @@
    + + +
    +
    +
    +

    Vulnerability Details

    + +
    +
    +
    +