diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 139d536f..c3636126 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -46,7 +46,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: ['8.1', '8.2', '8.3'] + php-version: ['8.3'] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: diff --git a/assets/css/bootstrap.min.css b/assets/css/bootstrap.min.css new file mode 100644 index 00000000..92e3fe87 --- /dev/null +++ b/assets/css/bootstrap.min.css @@ -0,0 +1,7 @@ +/*! + * Bootstrap v4.3.1 (https://getbootstrap.com/) + * Copyright 2011-2019 The Bootstrap Authors + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:center right calc(.375em + .1875rem);background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip,.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip{display:block}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip,.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip{display:block}.form-control-file.is-valid~.valid-feedback,.form-control-file.is-valid~.valid-tooltip,.was-validated .form-control-file:valid~.valid-feedback,.was-validated .form-control-file:valid~.valid-tooltip{display:block}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip,.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip,.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip{display:block}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E");background-repeat:no-repeat;background-position:center right calc(.375em + .1875rem);background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip,.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip{display:block}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip,.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip{display:block}.form-control-file.is-invalid~.invalid-feedback,.form-control-file.is-invalid~.invalid-tooltip,.was-validated .form-control-file:invalid~.invalid-feedback,.was-validated .form-control-file:invalid~.invalid-tooltip{display:block}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip,.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip,.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip{display:block}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:calc(1rem + .4rem);padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:-ms-flexbox;display:flex;-ms-flex:1 0 0%;flex:1 0 0%;-ms-flex-direction:column;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:first-of-type) .card-header:first-child{border-radius:0}.accordion>.card:not(:first-of-type):not(:last-of-type){border-bottom:0;border-radius:0}.accordion>.card:first-of-type{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:last-of-type{border-top-left-radius:0;border-top-right-radius:0}.accordion>.card .card-header{margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-sm .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-md .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-lg .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-xl .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush .list-group-item:last-child{margin-bottom:-1px}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{margin-bottom:0;border-bottom:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:1rem;border-top:1px solid #dee2e6;border-bottom-right-radius:.3rem;border-bottom-left-radius:.3rem}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:0s .6s opacity}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;overflow-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/assets/js/bootstrap.bundle.min.js b/assets/js/bootstrap.bundle.min.js new file mode 100644 index 00000000..43203684 --- /dev/null +++ b/assets/js/bootstrap.bundle.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v4.3.1 (https://getbootstrap.com/) + * Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery")):"function"==typeof define&&define.amd?define(["exports","jquery"],e):e((t=t||self).bootstrap={},t.jQuery)}(this,function(t,p){"use strict";function i(t,e){for(var n=0;nthis._items.length-1||t<0))if(this._isSliding)p(this._element).one(q.SLID,function(){return e.to(t)});else{if(n===t)return this.pause(),void this.cycle();var i=n=i.clientWidth&&n>=i.clientHeight}),h=0l[t]&&!i.escapeWithReference&&(n=Math.min(h[e],l[t]-("right"===t?h.width:h.height))),Kt({},e,n)}};return c.forEach(function(t){var e=-1!==["left","top"].indexOf(t)?"primary":"secondary";h=Qt({},h,u[e](t))}),t.offsets.popper=h,t},priority:["left","right","top","bottom"],padding:5,boundariesElement:"scrollParent"},keepTogether:{order:400,enabled:!0,fn:function(t){var e=t.offsets,n=e.popper,i=e.reference,o=t.placement.split("-")[0],r=Math.floor,s=-1!==["top","bottom"].indexOf(o),a=s?"right":"bottom",l=s?"left":"top",c=s?"width":"height";return n[a]r(i[a])&&(t.offsets.popper[l]=r(i[a])),t}},arrow:{order:500,enabled:!0,fn:function(t,e){var n;if(!fe(t.instance.modifiers,"arrow","keepTogether"))return t;var i=e.element;if("string"==typeof i){if(!(i=t.instance.popper.querySelector(i)))return t}else if(!t.instance.popper.contains(i))return console.warn("WARNING: `arrow.element` must be child of its popper element!"),t;var o=t.placement.split("-")[0],r=t.offsets,s=r.popper,a=r.reference,l=-1!==["left","right"].indexOf(o),c=l?"height":"width",h=l?"Top":"Left",u=h.toLowerCase(),f=l?"left":"top",d=l?"bottom":"right",p=Zt(i)[c];a[d]-ps[d]&&(t.offsets.popper[u]+=a[u]+p-s[d]),t.offsets.popper=Vt(t.offsets.popper);var m=a[u]+a[c]/2-p/2,g=Nt(t.instance.popper),_=parseFloat(g["margin"+h],10),v=parseFloat(g["border"+h+"Width"],10),y=m-t.offsets.popper[u]-_-v;return y=Math.max(Math.min(s[c]-p,y),0),t.arrowElement=i,t.offsets.arrow=(Kt(n={},u,Math.round(y)),Kt(n,f,""),n),t},element:"[x-arrow]"},flip:{order:600,enabled:!0,fn:function(p,m){if(oe(p.instance.modifiers,"inner"))return p;if(p.flipped&&p.placement===p.originalPlacement)return p;var g=Gt(p.instance.popper,p.instance.reference,m.padding,m.boundariesElement,p.positionFixed),_=p.placement.split("-")[0],v=te(_),y=p.placement.split("-")[1]||"",E=[];switch(m.behavior){case ge:E=[_,v];break;case _e:E=me(_);break;case ve:E=me(_,!0);break;default:E=m.behavior}return E.forEach(function(t,e){if(_!==t||E.length===e+1)return p;_=p.placement.split("-")[0],v=te(_);var n,i=p.offsets.popper,o=p.offsets.reference,r=Math.floor,s="left"===_&&r(i.right)>r(o.left)||"right"===_&&r(i.left)r(o.top)||"bottom"===_&&r(i.top)r(g.right),c=r(i.top)r(g.bottom),u="left"===_&&a||"right"===_&&l||"top"===_&&c||"bottom"===_&&h,f=-1!==["top","bottom"].indexOf(_),d=!!m.flipVariations&&(f&&"start"===y&&a||f&&"end"===y&&l||!f&&"start"===y&&c||!f&&"end"===y&&h);(s||u||d)&&(p.flipped=!0,(s||u)&&(_=E[e+1]),d&&(y="end"===(n=y)?"start":"start"===n?"end":n),p.placement=_+(y?"-"+y:""),p.offsets.popper=Qt({},p.offsets.popper,ee(p.instance.popper,p.offsets.reference,p.placement)),p=ie(p.instance.modifiers,p,"flip"))}),p},behavior:"flip",padding:5,boundariesElement:"viewport"},inner:{order:700,enabled:!1,fn:function(t){var e=t.placement,n=e.split("-")[0],i=t.offsets,o=i.popper,r=i.reference,s=-1!==["left","right"].indexOf(n),a=-1===["top","left"].indexOf(n);return o[s?"left":"top"]=r[n]-(a?o[s?"width":"height"]:0),t.placement=te(e),t.offsets.popper=Vt(o),t}},hide:{order:800,enabled:!0,fn:function(t){if(!fe(t.instance.modifiers,"hide","preventOverflow"))return t;var e=t.offsets.reference,n=ne(t.instance.modifiers,function(t){return"preventOverflow"===t.name}).boundaries;if(e.bottomn.right||e.top>n.bottom||e.rightdocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",sanitize:!0,sanitizeFn:null,whiteList:vn},Ln="show",xn="out",Pn={HIDE:"hide"+Tn,HIDDEN:"hidden"+Tn,SHOW:"show"+Tn,SHOWN:"shown"+Tn,INSERTED:"inserted"+Tn,CLICK:"click"+Tn,FOCUSIN:"focusin"+Tn,FOCUSOUT:"focusout"+Tn,MOUSEENTER:"mouseenter"+Tn,MOUSELEAVE:"mouseleave"+Tn},Hn="fade",jn="show",Rn=".tooltip-inner",Fn=".arrow",Mn="hover",Wn="focus",Un="click",Bn="manual",qn=function(){function i(t,e){if("undefined"==typeof be)throw new TypeError("Bootstrap's tooltips require Popper.js (https://popper.js.org/)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=p(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),p(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(p(this.getTipElement()).hasClass(jn))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),p.removeData(this.element,this.constructor.DATA_KEY),p(this.element).off(this.constructor.EVENT_KEY),p(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&p(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,(this._activeTrigger=null)!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===p(this.element).css("display"))throw new Error("Please use show on visible elements");var t=p.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){p(this.element).trigger(t);var n=m.findShadowRoot(this.element),i=p.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!i)return;var o=this.getTipElement(),r=m.getUID(this.constructor.NAME);o.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&p(o).addClass(Hn);var s="function"==typeof this.config.placement?this.config.placement.call(this,o,this.element):this.config.placement,a=this._getAttachment(s);this.addAttachmentClass(a);var l=this._getContainer();p(o).data(this.constructor.DATA_KEY,this),p.contains(this.element.ownerDocument.documentElement,this.tip)||p(o).appendTo(l),p(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new be(this.element,o,{placement:a,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:Fn},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}}),p(o).addClass(jn),"ontouchstart"in document.documentElement&&p(document.body).children().on("mouseover",null,p.noop);var c=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,p(e.element).trigger(e.constructor.Event.SHOWN),t===xn&&e._leave(null,e)};if(p(this.tip).hasClass(Hn)){var h=m.getTransitionDurationFromElement(this.tip);p(this.tip).one(m.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},t.hide=function(t){var e=this,n=this.getTipElement(),i=p.Event(this.constructor.Event.HIDE),o=function(){e._hoverState!==Ln&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),p(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(p(this.element).trigger(i),!i.isDefaultPrevented()){if(p(n).removeClass(jn),"ontouchstart"in document.documentElement&&p(document.body).children().off("mouseover",null,p.noop),this._activeTrigger[Un]=!1,this._activeTrigger[Wn]=!1,this._activeTrigger[Mn]=!1,p(this.tip).hasClass(Hn)){var r=m.getTransitionDurationFromElement(n);p(n).one(m.TRANSITION_END,o).emulateTransitionEnd(r)}else o();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){p(this.getTipElement()).addClass(Dn+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||p(this.config.template)[0],this.tip},t.setContent=function(){var t=this.getTipElement();this.setElementContent(p(t.querySelectorAll(Rn)),this.getTitle()),p(t).removeClass(Hn+" "+jn)},t.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=bn(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?p(e).parent().is(t)||t.empty().append(e):t.text(p(e).text())},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},t._getOffset=function(){var e=this,t={};return"function"==typeof this.config.offset?t.fn=function(t){return t.offsets=l({},t.offsets,e.config.offset(t.offsets,e.element)||{}),t}:t.offset=this.config.offset,t},t._getContainer=function(){return!1===this.config.container?document.body:m.isElement(this.config.container)?p(this.config.container):p(document).find(this.config.container)},t._getAttachment=function(t){return Nn[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)p(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==Bn){var e=t===Mn?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===Mn?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;p(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}}),p(this.element).closest(".modal").on("hide.bs.modal",function(){i.element&&i.hide()}),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||p(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),p(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Wn:Mn]=!0),p(e.getTipElement()).hasClass(jn)||e._hoverState===Ln?e._hoverState=Ln:(clearTimeout(e._timeout),e._hoverState=Ln,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===Ln&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||p(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),p(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Wn:Mn]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=xn,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===xn&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){var e=p(this.element).data();return Object.keys(e).forEach(function(t){-1!==An.indexOf(t)&&delete e[t]}),"number"==typeof(t=l({},this.constructor.Default,e,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),m.typeCheckConfig(wn,t,this.constructor.DefaultType),t.sanitize&&(t.template=bn(t.template,t.whiteList,t.sanitizeFn)),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=p(this.getTipElement()),e=t.attr("class").match(In);null!==e&&e.length&&t.removeClass(e.join(""))},t._handlePopperPlacementChange=function(t){var e=t.instance;this.tip=e.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},t._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(p(t).removeClass(Hn),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},i._jQueryInterface=function(n){return this.each(function(){var t=p(this).data(Cn),e="object"==typeof n&&n;if((t||!/dispose|hide/.test(n))&&(t||(t=new i(this,e),p(this).data(Cn,t)),"string"==typeof n)){if("undefined"==typeof t[n])throw new TypeError('No method named "'+n+'"');t[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.3.1"}},{key:"Default",get:function(){return kn}},{key:"NAME",get:function(){return wn}},{key:"DATA_KEY",get:function(){return Cn}},{key:"Event",get:function(){return Pn}},{key:"EVENT_KEY",get:function(){return Tn}},{key:"DefaultType",get:function(){return On}}]),i}();p.fn[wn]=qn._jQueryInterface,p.fn[wn].Constructor=qn,p.fn[wn].noConflict=function(){return p.fn[wn]=Sn,qn._jQueryInterface};var Kn="popover",Qn="bs.popover",Vn="."+Qn,Yn=p.fn[Kn],zn="bs-popover",Xn=new RegExp("(^|\\s)"+zn+"\\S+","g"),Gn=l({},qn.Default,{placement:"right",trigger:"click",content:"",template:''}),$n=l({},qn.DefaultType,{content:"(string|element|function)"}),Jn="fade",Zn="show",ti=".popover-header",ei=".popover-body",ni={HIDE:"hide"+Vn,HIDDEN:"hidden"+Vn,SHOW:"show"+Vn,SHOWN:"shown"+Vn,INSERTED:"inserted"+Vn,CLICK:"click"+Vn,FOCUSIN:"focusin"+Vn,FOCUSOUT:"focusout"+Vn,MOUSEENTER:"mouseenter"+Vn,MOUSELEAVE:"mouseleave"+Vn},ii=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),(e.prototype.constructor=e).__proto__=n;var o=i.prototype;return o.isWithContent=function(){return this.getTitle()||this._getContent()},o.addAttachmentClass=function(t){p(this.getTipElement()).addClass(zn+"-"+t)},o.getTipElement=function(){return this.tip=this.tip||p(this.config.template)[0],this.tip},o.setContent=function(){var t=p(this.getTipElement());this.setElementContent(t.find(ti),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(ei),e),t.removeClass(Jn+" "+Zn)},o._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},o._cleanTipClass=function(){var t=p(this.getTipElement()),e=t.attr("class").match(Xn);null!==e&&0=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t + diff --git a/composer.json b/composer.json index d347b06c..1ca71f4a 100644 --- a/composer.json +++ b/composer.json @@ -18,9 +18,9 @@ } ], "require": { - "php": ">=8.1", + "php": ">=8.3", "jtl/connector": "dev-master", - "symfony/yaml": "^3.4", + "symfony/yaml": "^6.4", "twbs/bootstrap": "^4.3", "theiconic/name-parser": "^0.1.1", "php-units-of-measure/php-units-of-measure": "^2.1", diff --git a/includes/JtlConnector.php b/includes/JtlConnector.php index 0a200c68..956ed54f 100644 --- a/includes/JtlConnector.php +++ b/includes/JtlConnector.php @@ -2,6 +2,10 @@ declare(strict_types=1); +if (!defined('ABSPATH')) { + exit; +} + use Jtl\Connector\Core\Application\Application; use Jtl\Connector\Core\Config\ConfigSchema; use Jtl\Connector\Core\Config\FileConfig; @@ -43,7 +47,7 @@ public static function capture_request(): void //phpcs:ignore PSR1.Methods.Camel if (!\is_string($features)) { throw new \InvalidArgumentException( - "Expected features to be a string but got " . \gettype($features) . " instead." + \esc_html("Expected features to be a string but got " . \gettype($features) . " instead.") ); } @@ -61,10 +65,12 @@ public static function capture_request(): void //phpcs:ignore PSR1.Methods.Camel */ private static function unslash_gpc(): void //phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps { + // phpcs:disable WordPress.Security.ValidatedSanitizedInput, WordPress.Security.NonceVerification.Recommended -- Intentional: reverse WordPress magic quotes for JTL Connector request processing $_GET = array_map('stripslashes_deep', $_GET); $_POST = array_map('stripslashes_deep', $_POST); $_COOKIE = array_map('stripslashes_deep', $_COOKIE); $_SERVER = array_map('stripslashes_deep', $_SERVER); $_REQUEST = array_map('stripslashes_deep', $_REQUEST); + // phpcs:enable } } diff --git a/includes/JtlConnectorAdmin.php b/includes/JtlConnectorAdmin.php index 090c404a..f1bc5fba 100644 --- a/includes/JtlConnectorAdmin.php +++ b/includes/JtlConnectorAdmin.php @@ -11,6 +11,7 @@ use JtlWooCommerceConnector\Utilities\Config; use JtlWooCommerceConnector\Utilities\Db; use JtlWooCommerceConnector\Utilities\Id; +use JtlWooCommerceConnector\Utilities\LinkTableNames; use JtlWooCommerceConnector\Utilities\SqlHelper; use JtlWooCommerceConnector\Utilities\SupportedPlugins; use JtlWooCommerceConnector\Utilities\Util; @@ -44,7 +45,7 @@ public static function plugin_activation(): void //phpcs:ignore PSR1.Methods.Cam $version = $woocommerce->version; $buildVersion = Config::getBuildVersion(); - clearConnectorCache(false); + jtlwcc_clear_connector_cache(false); $parsedFile = (array) Yaml::parseFile(JTLWCC_CONNECTOR_DIR . '/build-config.yaml'); @@ -70,7 +71,7 @@ public static function plugin_activation(): void //phpcs:ignore PSR1.Methods.Cam } catch (MissingRequirementException $exc) { if (is_admin() && ( ! defined('DOING_AJAX') || ! DOING_AJAX )) { jtlwcc_deactivate_plugin(); - wp_die($exc->getMessage()); + wp_die(esc_html($exc->getMessage())); } else { return; } @@ -149,7 +150,7 @@ private static function activate_linking(): void //phpcs:ignore PSR1.Methods.Cam } if ($oldExists && $newExists) { - $wpdb->query(sprintf($dropOldQuery, $oldPrefix . $table)); + $wpdb->query("DROP TABLE IF EXISTS `" . esc_sql($oldPrefix . $table) . "`"); } elseif (! $oldExists && ! $newExists) { if (strcmp($table, 'category_level') === 0) { self::activate_category_tree($db); @@ -166,7 +167,13 @@ private static function activate_linking(): void //phpcs:ignore PSR1.Methods.Cam } elseif (strcmp($table, 'tax_class') === 0) { self::createTaxClassLinkingTable(); } else { - $wpdb->query(sprintf($createQuery, $prefix . $table)); + $wpdb->query(" + CREATE TABLE IF NOT EXISTS `" . esc_sql($prefix . $table) . "` ( + `endpoint_id` BIGINT(20) unsigned NOT NULL, + `host_id` INT(10) unsigned NOT NULL, + PRIMARY KEY (`endpoint_id`, `host_id`), + INDEX (`host_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"); } /** @phpstan-ignore booleanNot.alwaysTrue */ } elseif ($oldExists && !$newExists) { @@ -217,10 +224,7 @@ private static function renameTable(string $oldName, string $newName): void { global $wpdb; - $query = 'RENAME TABLE %s TO %s;'; - - $sql = sprintf($query, $oldName, $newName); - $wpdb->query($sql); + $wpdb->query("RENAME TABLE `" . esc_sql($oldName) . "` TO `" . esc_sql($newName) . "`"); } /** @@ -232,30 +236,34 @@ private static function activate_category_tree(Db $db): void //phpcs:ignore { $wpdb = $db->getWpDb(); $prefix = $wpdb->prefix . 'jtl_connector_'; - $engine = $wpdb->get_var(sprintf( + $engine = $wpdb->get_var($wpdb->prepare( "SELECT ENGINE FROM information_schema.TABLES - WHERE TABLE_NAME = '{$wpdb->terms}' AND TABLE_SCHEMA = '%s'", + WHERE TABLE_NAME = %s AND TABLE_SCHEMA = %s", + $wpdb->terms, DB_NAME )); - $constraint = ''; - - if ($engine === 'InnoDB') { - if (!$db->checkIfFKExists($prefix . 'category_level', 'jtl_connector_category_level1')) { - $constraint = ", CONSTRAINT `jtl_connector_category_level1` FOREIGN KEY (`category_id`) - REFERENCES {$wpdb->terms} (`term_id`) ON DELETE CASCADE ON UPDATE NO ACTION"; - } - } - + // phpcs:ignore WordPress.DB -- esc_sql returns string for string input $wpdb->query(" - CREATE TABLE IF NOT EXISTS `{$prefix}category_level` ( + CREATE TABLE IF NOT EXISTS `" . esc_sql($prefix) . "category_level` ( `category_id` BIGINT(20) unsigned NOT NULL, `level` int(10) unsigned NOT NULL, `sort` int(10) unsigned NOT NULL, PRIMARY KEY (`category_id`), - INDEX (`level`) {$constraint} + INDEX (`level`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"); + + if ($engine === 'InnoDB') { + if (!$db->checkIfFKExists($prefix . 'category_level', 'jtl_connector_category_level1')) { + // phpcs:ignore WordPress.DB -- esc_sql returns string for string input + $wpdb->query( + "ALTER TABLE `" . esc_sql($prefix) . "category_level` + ADD CONSTRAINT `jtl_connector_category_level1` FOREIGN KEY (`category_id`) + REFERENCES `" . esc_sql($wpdb->terms) . "` (`term_id`) ON DELETE CASCADE ON UPDATE NO ACTION" + ); + } + } } /** @@ -267,27 +275,34 @@ private static function activate_checksum(string $prefix): void //phpcs:ignore { global $wpdb; - $engine = $wpdb->get_var(sprintf( + $engine = $wpdb->get_var($wpdb->prepare( "SELECT ENGINE FROM information_schema.TABLES - WHERE TABLE_NAME = '{$wpdb->posts}' AND TABLE_SCHEMA = '%s'", + WHERE TABLE_NAME = %s AND TABLE_SCHEMA = %s", + $wpdb->posts, DB_NAME )); - if ($engine === 'InnoDB') { - $constraint = ", CONSTRAINT `jtl_connector_product_checksum1` FOREIGN KEY (`product_id`) - REFERENCES {$wpdb->posts} (`ID`) ON DELETE CASCADE ON UPDATE NO ACTION"; - } else { - $constraint = ''; - } - + // phpcs:ignore WordPress.DB -- esc_sql returns string for string input $wpdb->query(" - CREATE TABLE IF NOT EXISTS `{$prefix}product_checksum` ( + CREATE TABLE IF NOT EXISTS `" . esc_sql($prefix) . "product_checksum` ( `product_id` BIGINT(20) unsigned NOT NULL, `type` tinyint unsigned NOT NULL, `checksum` varchar(255) NOT NULL, - PRIMARY KEY (`product_id`) {$constraint} + PRIMARY KEY (`product_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"); + + if ($engine === 'InnoDB') { + $db = new Db($wpdb); + if (!$db->checkIfFKExists($prefix . 'product_checksum', 'jtl_connector_product_checksum1')) { + // phpcs:ignore WordPress.DB -- esc_sql returns string for string input + $wpdb->query( + "ALTER TABLE `" . esc_sql($prefix) . "product_checksum` + ADD CONSTRAINT `jtl_connector_product_checksum1` FOREIGN KEY (`product_id`) + REFERENCES `" . esc_sql($wpdb->posts) . "` (`ID`) ON DELETE CASCADE ON UPDATE NO ACTION" + ); + } + } } /** @@ -297,7 +312,7 @@ private static function createCustomerLinkingTable(): void { global $wpdb; $wpdb->query(' - CREATE TABLE IF NOT EXISTS `jtl_connector_link_customer` ( + CREATE TABLE IF NOT EXISTS `' . esc_sql(LinkTableNames::CUSTOMER) . '` ( `endpoint_id` VARCHAR(255) NOT NULL, `host_id` INT(10) unsigned NOT NULL, `is_guest` BIT, @@ -314,7 +329,7 @@ private static function createCustomerGroupLinkingTable(): void { global $wpdb; $wpdb->query(' - CREATE TABLE IF NOT EXISTS `jtl_connector_link_customer_group` ( + CREATE TABLE IF NOT EXISTS `' . esc_sql(LinkTableNames::CUSTOMER_GROUP) . '` ( `endpoint_id` VARCHAR(255) NOT NULL, `host_id` INT(10) unsigned NOT NULL, PRIMARY KEY (`endpoint_id`, `host_id`), @@ -330,7 +345,7 @@ private static function createImageLinkingTable(): void { global $wpdb; $wpdb->query(' - CREATE TABLE IF NOT EXISTS `jtl_connector_link_image` ( + CREATE TABLE IF NOT EXISTS `' . esc_sql(LinkTableNames::IMAGE) . '` ( `endpoint_id` VARCHAR(255) NOT NULL, `host_id` INT(10) NOT NULL, `type` INT unsigned NOT NULL, @@ -349,34 +364,34 @@ private static function createManufacturerLinkingTable(Db $db): void { global $wpdb; - $query = ' - CREATE TABLE IF NOT EXISTS `%s` ( + $wpdb->query(" + CREATE TABLE IF NOT EXISTS `" . esc_sql($wpdb->prefix . LinkTableNames::MANUFACTURER) . "` ( `endpoint_id` BIGINT(20) unsigned NOT NULL, `host_id` INT(10) unsigned NOT NULL, PRIMARY KEY (`endpoint_id`, `host_id`), INDEX (`host_id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'; - - $wpdb->query(sprintf($query, $wpdb->prefix . 'jtl_connector_link_manufacturer')); + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"); - $engine = $wpdb->get_var(sprintf( + $engine = $wpdb->get_var($wpdb->prepare( "SELECT ENGINE FROM information_schema.TABLES - WHERE TABLE_NAME = '{$wpdb->posts}' AND TABLE_SCHEMA = '%s'", + WHERE TABLE_NAME = %s AND TABLE_SCHEMA = %s", + $wpdb->posts, DB_NAME )); if ($engine === 'InnoDB') { if ( !$db->checkIfFKExists( - $wpdb->prefix . 'jtl_connector_link_manufacturer', + $wpdb->prefix . LinkTableNames::MANUFACTURER, 'jtl_connector_link_manufacturer_1' ) ) { - $wpdb->query(" - ALTER TABLE `{$wpdb->prefix}jtl_connector_link_manufacturer` - ADD CONSTRAINT `jtl_connector_link_manufacturer_1` FOREIGN KEY (`endpoint_id`) - REFERENCES `{$wpdb->terms}` (`term_id`) ON DELETE CASCADE ON UPDATE NO ACTION"); + $wpdb->query( + "ALTER TABLE `" . esc_sql($wpdb->prefix . LinkTableNames::MANUFACTURER) . "` + ADD CONSTRAINT `jtl_connector_link_manufacturer_1` FOREIGN KEY (`endpoint_id`) + REFERENCES `" . esc_sql($wpdb->terms) . "` (`term_id`) ON DELETE CASCADE ON UPDATE NO ACTION" + ); } } } @@ -388,15 +403,14 @@ protected static function createTaxClassLinkingTable(): void { global $wpdb; - $query = ' - CREATE TABLE IF NOT EXISTS `%s%s` ( + $wpdb->query( + "CREATE TABLE IF NOT EXISTS `" . esc_sql($wpdb->prefix . LinkTableNames::TAX_CLASS) . "` ( `endpoint_id` VARCHAR(200) NOT NULL, `host_id` INT(10) unsigned NOT NULL, PRIMARY KEY (`endpoint_id`), UNIQUE (`host_id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'; - - $wpdb->query(sprintf($query, $wpdb->prefix, 'jtl_connector_link_tax_class')); + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" + ); } // @@ -411,56 +425,63 @@ private static function add_constraints_for_multi_linking_tables(string $prefix, { global $wpdb; - $engine = $wpdb->get_var(sprintf( + $engine = $wpdb->get_var($wpdb->prepare( "SELECT ENGINE FROM information_schema.TABLES - WHERE TABLE_NAME = '{$wpdb->posts}' AND TABLE_SCHEMA = '%s'", + WHERE TABLE_NAME = %s AND TABLE_SCHEMA = %s", + $wpdb->posts, DB_NAME )); if ($engine === 'InnoDB') { if (! $db->checkIfFKExists($prefix . 'product', 'jtl_connector_link_product_1')) { + // phpcs:ignore WordPress.DB -- esc_sql returns string for string input $wpdb->query( - "ALTER TABLE `{$prefix}product` - ADD CONSTRAINT `jtl_connector_link_product_1` FOREIGN KEY (`endpoint_id`) - REFERENCES `{$wpdb->posts}` (`ID`) ON DELETE CASCADE ON UPDATE NO ACTION" + "ALTER TABLE `" . esc_sql($prefix) . "product` + ADD CONSTRAINT `jtl_connector_link_product_1` FOREIGN KEY (`endpoint_id`) + REFERENCES `" . esc_sql($wpdb->posts) . "` (`ID`) ON DELETE CASCADE ON UPDATE NO ACTION" ); } if (! $db->checkIfFKExists($prefix . 'order', 'jtl_connector_link_order_1')) { + // phpcs:ignore WordPress.DB -- esc_sql returns string for string input $wpdb->query( - "ALTER TABLE `{$prefix}order` - ADD CONSTRAINT `jtl_connector_link_order_1` FOREIGN KEY (`endpoint_id`) - REFERENCES `{$wpdb->posts}` (`ID`) ON DELETE CASCADE ON UPDATE NO ACTION" + "ALTER TABLE `" . esc_sql($prefix) . "order` + ADD CONSTRAINT `jtl_connector_link_order_1` FOREIGN KEY (`endpoint_id`) + REFERENCES `" . esc_sql($wpdb->posts) . "` (`ID`) ON DELETE CASCADE ON UPDATE NO ACTION" ); } if (! $db->checkIfFKExists($prefix . 'payment', 'jtl_connector_link_payment_1')) { + // phpcs:ignore WordPress.DB -- esc_sql returns string for string input $wpdb->query( - "ALTER TABLE `{$prefix}payment` - ADD CONSTRAINT `jtl_connector_link_payment_1` FOREIGN KEY (`endpoint_id`) - REFERENCES `{$wpdb->posts}` (`ID`) ON DELETE CASCADE ON UPDATE NO ACTION" + "ALTER TABLE `" . esc_sql($prefix) . "payment` + ADD CONSTRAINT `jtl_connector_link_payment_1` FOREIGN KEY (`endpoint_id`) + REFERENCES `" . esc_sql($wpdb->posts) . "` (`ID`) ON DELETE CASCADE ON UPDATE NO ACTION" ); } if (! $db->checkIfFKExists($prefix . 'crossselling', 'jtl_connector_link_crossselling_1')) { + // phpcs:ignore WordPress.DB -- esc_sql returns string for string input $wpdb->query( - "ALTER TABLE `{$prefix}crossselling` - ADD CONSTRAINT `jtl_connector_link_crossselling_1` FOREIGN KEY (`endpoint_id`) - REFERENCES `{$wpdb->posts}` (`ID`) ON DELETE CASCADE ON UPDATE NO ACTION" + "ALTER TABLE `" . esc_sql($prefix) . "crossselling` + ADD CONSTRAINT `jtl_connector_link_crossselling_1` FOREIGN KEY (`endpoint_id`) + REFERENCES `" . esc_sql($wpdb->posts) . "` (`ID`) ON DELETE CASCADE ON UPDATE NO ACTION" ); } if (! $db->checkIfFKExists($prefix . 'category', 'jtl_connector_link_category_1')) { + // phpcs:ignore WordPress.DB -- esc_sql returns string for string input $wpdb->query( - "ALTER TABLE `{$prefix}category` - ADD CONSTRAINT `jtl_connector_link_category_1` FOREIGN KEY (`endpoint_id`) - REFERENCES `{$wpdb->terms}` (`term_id`) ON DELETE CASCADE ON UPDATE NO ACTION" + "ALTER TABLE `" . esc_sql($prefix) . "category` + ADD CONSTRAINT `jtl_connector_link_category_1` FOREIGN KEY (`endpoint_id`) + REFERENCES `" . esc_sql($wpdb->terms) . "` (`term_id`) ON DELETE CASCADE ON UPDATE NO ACTION" ); } - - $table = $wpdb->prefix . 'woocommerce_attribute_taxonomies'; if (! $db->checkIfFKExists($prefix . 'specific', 'jtl_connector_link_specific_1')) { + // phpcs:ignore WordPress.DB -- esc_sql returns string for string input $wpdb->query( - "ALTER TABLE `{$prefix}specific` - ADD CONSTRAINT `jtl_connector_link_specific_1` FOREIGN KEY (`endpoint_id`) - REFERENCES `{$table}` (`attribute_id`) ON DELETE CASCADE ON UPDATE NO ACTION" + "ALTER TABLE `" . esc_sql($prefix) . "specific` + ADD CONSTRAINT `jtl_connector_link_specific_1` FOREIGN KEY (`endpoint_id`) + REFERENCES `" + . esc_sql($wpdb->prefix . 'woocommerce_attribute_taxonomies') + . "` (`attribute_id`) ON DELETE CASCADE ON UPDATE NO ACTION" ); } } @@ -496,14 +517,14 @@ private static function create_password(): string //phpcs:ignore PSR1.Methods.Ca return sprintf( '%04X%04X-%04X-%04X-%04X-%04X%04X%04X', - mt_rand(0, 65535), - mt_rand(0, 65535), - mt_rand(0, 65535), - mt_rand(16384, 20479), - mt_rand(32768, 49151), - mt_rand(0, 65535), - mt_rand(0, 65535), - mt_rand(0, 65535) + random_int(0, 65535), + random_int(0, 65535), + random_int(0, 65535), + random_int(16384, 20479), + random_int(32768, 49151), + random_int(0, 65535), + random_int(0, 65535), + random_int(0, 65535) ); } @@ -520,7 +541,7 @@ public static function loadFeaturesJson(string $featuresJsonPath): void if (is_array($featuresJson)) { $saveResult = file_put_contents($featuresJsonPath, json_encode($featuresJson, JSON_PRETTY_PRINT)); if ($saveResult === false) { - throw new Exception(sprintf("Cannot save features in %s file.", $featuresJsonPath), 100); + throw new Exception(sprintf("Cannot save features in %s file.", esc_html($featuresJsonPath)), 100); } } } else { @@ -685,7 +706,9 @@ public static function init_hooks(Db $db): void //phpcs:ignore PSR1.Methods.Came wp_enqueue_style( 'bootstrap4', - 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css' + JTLWCC_CONNECTOR_DIR_URL . '/assets/css/bootstrap.min.css', + [], + '4.3.1' ); wp_enqueue_style( 'custom-css-jtl', @@ -693,9 +716,9 @@ public static function init_hooks(Db $db): void //phpcs:ignore PSR1.Methods.Came ); wp_enqueue_script( 'boot1', - 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.bundle.min.js', + JTLWCC_CONNECTOR_DIR_URL . '/assets/js/bootstrap.bundle.min.js', [ 'jquery' ], - '', + '4.3.1', true ); }; @@ -705,14 +728,14 @@ public static function init_hooks(Db $db): void //phpcs:ignore PSR1.Methods.Came $wooJtlConnectorInformationPage = function (): void { JtlConnectorAdmin::displayPageNew( 'information_page', - __('Connector information', JTLWCC_TEXT_DOMAIN) + __('Connector information', 'woo-jtl-connector') ); }; $wooJtlConnectorAdvancedPage = function (): void { JtlConnectorAdmin::displayPageNew( 'advanced_page', - __('Advanced Settings', JTLWCC_TEXT_DOMAIN), + __('Advanced Settings', 'woo-jtl-connector'), true ); }; @@ -720,7 +743,7 @@ public static function init_hooks(Db $db): void //phpcs:ignore PSR1.Methods.Came $wooJtlConnectorDeliveryTimePage = function (): void { JtlConnectorAdmin::displayPageNew( 'delivery_time_page', - __('Delivery time', JTLWCC_TEXT_DOMAIN), + __('Delivery time', 'woo-jtl-connector'), true ); }; @@ -728,7 +751,7 @@ public static function init_hooks(Db $db): void //phpcs:ignore PSR1.Methods.Came $wooJtlConnectorCustomerOrderPage = function (): void { JtlConnectorAdmin::displayPageNew( 'customer_order_page', - __('Customer order', JTLWCC_TEXT_DOMAIN), + __('Customer order', 'woo-jtl-connector'), true ); }; @@ -736,7 +759,7 @@ public static function init_hooks(Db $db): void //phpcs:ignore PSR1.Methods.Came $wooJtlConnectorCustomersPage = function (): void { JtlConnectorAdmin::displayPageNew( 'customers_page', - __('Customers', JTLWCC_TEXT_DOMAIN), + __('Customers', 'woo-jtl-connector'), true ); }; @@ -744,14 +767,14 @@ public static function init_hooks(Db $db): void //phpcs:ignore PSR1.Methods.Came $wooJtlConnectorDeveloperSettingsPage = function (): void { JtlConnectorAdmin::displayPageNew( 'developer_settings_page', - __('Developer Settings', JTLWCC_TEXT_DOMAIN), + __('Developer Settings', 'woo-jtl-connector'), true ); }; add_menu_page( - __('JTL-Connector', JTLWCC_TEXT_DOMAIN), - __('JTL-Connector', JTLWCC_TEXT_DOMAIN), + __('JTL-Connector', 'woo-jtl-connector'), + __('JTL-Connector', 'woo-jtl-connector'), 'manage_woocommerce', 'woo-jtl-connector', function (): void { @@ -762,8 +785,8 @@ function (): void { add_submenu_page( 'woo-jtl-connector', - __('JTL-Connector:Information', JTLWCC_TEXT_DOMAIN), - __('Information', JTLWCC_TEXT_DOMAIN), + __('JTL-Connector:Information', 'woo-jtl-connector'), + __('Information', 'woo-jtl-connector'), 'manage_woocommerce', 'woo-jtl-connector-information', function () use ($wooJtlConnectorInformationPage): void { @@ -773,8 +796,8 @@ function () use ($wooJtlConnectorInformationPage): void { add_submenu_page( 'woo-jtl-connector', - __('JTL-Connector:Advanced', JTLWCC_TEXT_DOMAIN), - __('Advanced Settings', JTLWCC_TEXT_DOMAIN), + __('JTL-Connector:Advanced', 'woo-jtl-connector'), + __('Advanced Settings', 'woo-jtl-connector'), 'manage_woocommerce', 'woo-jtl-connector-advanced', function () use ($wooJtlConnectorAdvancedPage): void { @@ -784,8 +807,8 @@ function () use ($wooJtlConnectorAdvancedPage): void { add_submenu_page( 'woo-jtl-connector', - __('JTL-Connector:Delivery times', JTLWCC_TEXT_DOMAIN), - __('Delivery times', JTLWCC_TEXT_DOMAIN), + __('JTL-Connector:Delivery times', 'woo-jtl-connector'), + __('Delivery times', 'woo-jtl-connector'), 'manage_woocommerce', 'woo-jtl-connector-delivery-time', function () use ($wooJtlConnectorDeliveryTimePage): void { @@ -795,8 +818,8 @@ function () use ($wooJtlConnectorDeliveryTimePage): void { add_submenu_page( 'woo-jtl-connector', - __('JTL-Connector:Customer orders', JTLWCC_TEXT_DOMAIN), - __('Customer orders', JTLWCC_TEXT_DOMAIN), + __('JTL-Connector:Customer orders', 'woo-jtl-connector'), + __('Customer orders', 'woo-jtl-connector'), 'manage_woocommerce', 'woo-jtl-connector-customer-order', function () use ($wooJtlConnectorCustomerOrderPage): void { @@ -806,8 +829,8 @@ function () use ($wooJtlConnectorCustomerOrderPage): void { add_submenu_page( 'woo-jtl-connector', - __('JTL-Connector:Customers', JTLWCC_TEXT_DOMAIN), - __('Customers', JTLWCC_TEXT_DOMAIN), + __('JTL-Connector:Customers', 'woo-jtl-connector'), + __('Customers', 'woo-jtl-connector'), 'manage_woocommerce', 'woo-jtl-connector-customers', function () use ($wooJtlConnectorCustomersPage): void { @@ -817,8 +840,8 @@ function () use ($wooJtlConnectorCustomersPage): void { add_submenu_page( 'woo-jtl-connector', - __('JTL-Connector:Developer Settings', JTLWCC_TEXT_DOMAIN), - __('Developer Settings', JTLWCC_TEXT_DOMAIN), + __('JTL-Connector:Developer Settings', 'woo-jtl-connector'), + __('Developer Settings', 'woo-jtl-connector'), 'manage_woocommerce', 'woo-jtl-connector-developer-settings', function () use ($wooJtlConnectorDeveloperSettingsPage): void { @@ -945,11 +968,12 @@ public static function displayPageNew(
?action=settings_save_woo-jtl-connector" enctype="multipart/form-data"> +
-

+

@@ -999,7 +1023,7 @@ private static function getInformationFields(): array They help you to process more orders in a shorter time and offer a range of exciting functionalities. Basic information and credentials of the installed JTL-Connector. It is needed to configure the JTL-Connector in the jtl customer center and JTL-Wawi.', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), ]; @@ -1014,37 +1038,19 @@ private static function getInformationFields(): array 'type' => 'connector_url', 'helpBlock' => __( 'This URL should be placed in the JTL-Customer-Center and in your JTL-Wawi as "Onlineshop-URL".', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), 'id' => 'connector_url', - 'value' => sprintf( - '%s%s%s', - $protocol = isset($_SERVER['HTTPS']) - && ( $_SERVER['HTTPS'] == 'on' - || $_SERVER['HTTPS'] == 1 ) - || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) - && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' - ? 'https://' : 'http://', - str_replace( - 'http://', - '', - str_replace( - 'https://', - '', - get_bloginfo('url') - ) - ), - '/index.php/jtlconnector/' - ), + 'value' => esc_url(get_bloginfo('url') . '/index.php/jtlconnector/'), ]; //Add connector password field $fields[] = [ - 'title' => __('Connector Password', JTLWCC_TEXT_DOMAIN), + 'title' => __('Connector Password', 'woo-jtl-connector'), 'type' => 'connector_password', 'helpBlock' => __( 'This secret password will be used for identifying that your JTL-Wawi ist allowed to pull/push data.', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), 'id' => 'connector_password', 'value' => Config::get(Config::OPTIONS_TOKEN), @@ -1054,7 +1060,7 @@ private static function getInformationFields(): array $fields[] = [ 'title' => 'Connector Version', 'type' => 'paragraph', - 'helpBlock' => __('This is your current installed connector version.', JTLWCC_TEXT_DOMAIN), + 'helpBlock' => __('This is your current installed connector version.', 'woo-jtl-connector'), 'desc' => Config::get(Config::OPTIONS_INSTALLED_VERSION), ]; @@ -1066,7 +1072,7 @@ private static function getInformationFields(): array //Add extend plugin informations if (\count(SupportedPlugins::getSupported()) > 0) { $fields[] = [ - 'title' => __('These activated plugins extend the JTL-Connector:', JTLWCC_TEXT_DOMAIN), + 'title' => __('These activated plugins extend the JTL-Connector:', 'woo-jtl-connector'), 'type' => 'compatible_plugins_field', 'plugins' => SupportedPlugins::getSupported(), ]; @@ -1074,13 +1080,13 @@ private static function getInformationFields(): array //Add Incompatible plugin informations $fields[] = [ - 'title' => __('Incompatible with these plugins:', JTLWCC_TEXT_DOMAIN), + 'title' => __('Incompatible with these plugins:', 'woo-jtl-connector'), 'type' => 'not_compatible_plugins_field', 'plugins' => SupportedPlugins::getNotSupportedButActive(false, true, true), ]; $fields[] = [ - 'title' => __('Important information', JTLWCC_TEXT_DOMAIN), + 'title' => __('Important information', 'woo-jtl-connector'), 'type' => 'jtlwcc_card', 'color' => 'border-warning', 'text-color' => 'text-warning', @@ -1088,7 +1094,7 @@ private static function getInformationFields(): array 'text' => __( 'Similar plugins, like the not compatible plugins which are listed here, might be incompatible too!', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), ]; @@ -1112,7 +1118,9 @@ private static function notCompatiblePluginsError(): void if (\is_string($notSupportedButActiveAsString)) { self::jtlwcc_show_wordpress_error( sprintf( - __('The listed plugins can cause problems when using the connector: %s', JTLWCC_TEXT_DOMAIN), + // translators: %s: list of unsupported plugin names + + __('The listed plugins can cause problems when using the connector: %s', 'woo-jtl-connector'), $notSupportedButActiveAsString ) ); @@ -1128,7 +1136,8 @@ private static function notCompatiblePluginsError(): void public static function jtlwcc_show_wordpress_error(string $message): void //phpcs:ignore { echo ''; } @@ -1148,7 +1157,7 @@ private static function getAdvancedFields(): array 'With JTL-Connector for WooCommerce, you can connect your WooCommerce online shop with the free JTL-Wawi ERP system by JTL-Software. These are the advanced settings of the installed JTL-Connector. Here you can configure how some data is handled while push/pull.', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), ]; @@ -1159,73 +1168,73 @@ private static function getAdvancedFields(): array //Add variation specific radio field $fields[] = [ - 'title' => __('Variation specifics', JTLWCC_TEXT_DOMAIN), + 'title' => __('Variation specifics', 'woo-jtl-connector'), 'type' => 'active_true_false_radio', 'desc' => __( 'Enable if you want to show your customers the variation as specific (Default : Enabled).', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), 'id' => Config::OPTIONS_SHOW_VARIATION_SPECIFICS_ON_PRODUCT_PAGE, 'value' => Config::get(Config::OPTIONS_SHOW_VARIATION_SPECIFICS_ON_PRODUCT_PAGE), - 'trueText' => __('Enabled', JTLWCC_TEXT_DOMAIN), - 'falseText' => __('Disabled', JTLWCC_TEXT_DOMAIN), + 'trueText' => __('Enabled', 'woo-jtl-connector'), + 'falseText' => __('Disabled', 'woo-jtl-connector'), ]; $fields[] = [ - 'title' => __('Delete unknown attributes', JTLWCC_TEXT_DOMAIN), + 'title' => __('Delete unknown attributes', 'woo-jtl-connector'), 'type' => 'active_true_false_radio', 'desc' => __( 'Enable if you want to delete unknown attributes on push (Default : Disabled).', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), 'id' => Config::OPTIONS_DELETE_UNKNOWN_ATTRIBUTES, 'value' => Config::get(Config::OPTIONS_DELETE_UNKNOWN_ATTRIBUTES), - 'trueText' => __('Enabled', JTLWCC_TEXT_DOMAIN), - 'falseText' => __('Disabled', JTLWCC_TEXT_DOMAIN), + 'trueText' => __('Enabled', 'woo-jtl-connector'), + 'falseText' => __('Disabled', 'woo-jtl-connector'), ]; //Add custom properties radio field $fields[] = [ - 'title' => __('Custom properties', JTLWCC_TEXT_DOMAIN), + 'title' => __('Custom properties', 'woo-jtl-connector'), 'type' => 'active_true_false_radio', 'desc' => __( 'If you activate this option, custom fields from JTL-Wawi will be handled as attributes in the shop. After changing this option, full-sync is required (Default : Enabled).', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), 'id' => Config::OPTIONS_SEND_CUSTOM_PROPERTIES, 'value' => Config::get(Config::OPTIONS_SEND_CUSTOM_PROPERTIES), - 'trueText' => __('Enabled', JTLWCC_TEXT_DOMAIN), - 'falseText' => __('Disabled', JTLWCC_TEXT_DOMAIN), + 'trueText' => __('Enabled', 'woo-jtl-connector'), + 'falseText' => __('Disabled', 'woo-jtl-connector'), ]; //Add gtin/ean radio field $fields[] = [ - 'title' => __('GTIN / EAN', JTLWCC_TEXT_DOMAIN), + 'title' => __('GTIN / EAN', 'woo-jtl-connector'), 'type' => 'active_true_false_radio', 'desc' => __( 'Enable if you want to use the GTIN field for ean. (Default : Enabled / Required plugin: WooCommerce Germanized).', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), 'id' => Config::OPTIONS_USE_GTIN_FOR_EAN, 'value' => Config::get(Config::OPTIONS_USE_GTIN_FOR_EAN), - 'trueText' => __('Enabled', JTLWCC_TEXT_DOMAIN), - 'falseText' => __('Disabled', JTLWCC_TEXT_DOMAIN), + 'trueText' => __('Enabled', 'woo-jtl-connector'), + 'falseText' => __('Disabled', 'woo-jtl-connector'), ]; //Allow html in attributes $fields[] = [ - 'title' => __('Allow HTML in product attributes', JTLWCC_TEXT_DOMAIN), + 'title' => __('Allow HTML in product attributes', 'woo-jtl-connector'), 'type' => 'active_true_false_radio', 'desc' => __( 'Enable if you want to allow saving HTML in product attributes (Default : Disabled)', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), 'id' => Config::OPTIONS_ALLOW_HTML_IN_PRODUCT_ATTRIBUTES, 'value' => Config::get(Config::OPTIONS_ALLOW_HTML_IN_PRODUCT_ATTRIBUTES), - 'trueText' => __('Enabled', JTLWCC_TEXT_DOMAIN), - 'falseText' => __('Disabled', JTLWCC_TEXT_DOMAIN), + 'trueText' => __('Enabled', 'woo-jtl-connector'), + 'falseText' => __('Disabled', 'woo-jtl-connector'), ]; //Add sectionend @@ -1253,7 +1262,7 @@ private static function getDeliveryTimeFields(): array 'With JTL-Connector for WooCommerce, you can connect your WooCommerce online shop with the free JTL-Wawi ERP system by JTL-Software. Delivery time related settings of the installed JTL-Connector. Here you can set some options to modify the pull/psuh of delivery times.', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), ]; @@ -1264,14 +1273,14 @@ private static function getDeliveryTimeFields(): array //Add delivery time calculation radio field $fields[] = [ - 'title' => __('DeliveryTime Calculation', JTLWCC_TEXT_DOMAIN), + 'title' => __('DeliveryTime Calculation', 'woo-jtl-connector'), 'type' => 'jtl_connector_select', 'id' => Config::OPTIONS_USE_DELIVERYTIME_CALC, 'value' => Config::get(Config::OPTIONS_USE_DELIVERYTIME_CALC), 'options' => [ - 'delivery_time_calc' => __('Lieferzeit Berechnung nutzen', JTLWCC_TEXT_DOMAIN), - 'delivery_status' => __('Lieferstatus nutzen', JTLWCC_TEXT_DOMAIN), - 'deactivated' => __('Deaktiviert', JTLWCC_TEXT_DOMAIN), + 'delivery_time_calc' => __('Lieferzeit Berechnung nutzen', 'woo-jtl-connector'), + 'delivery_status' => __('Lieferstatus nutzen', 'woo-jtl-connector'), + 'deactivated' => __('Deaktiviert', 'woo-jtl-connector'), ], 'helpBlock' => __( "Enable if you want to use delivery time calculation.
@@ -1279,55 +1288,55 @@ private static function getDeliveryTimeFields(): array Delivery status: Use the delivery status as delivery time.
Deactivated: Don't use delivery time.
(Default : Delivery time calculation / Required plugin: WooCommerce Germanized).", - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), ]; //Add dont use zero values radio field $fields[] = [ - 'title' => __('Dont use zero values for delivery time', JTLWCC_TEXT_DOMAIN), + 'title' => __('Dont use zero values for delivery time', 'woo-jtl-connector'), 'type' => 'active_true_false_radio', 'desc' => __( 'Enable if you dont want to use zero values for delivery time. (Default : Enabled).', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), 'id' => Config::OPTIONS_DISABLED_ZERO_DELIVERY_TIME, 'value' => Config::get(Config::OPTIONS_DISABLED_ZERO_DELIVERY_TIME), - 'trueText' => __('Enabled', JTLWCC_TEXT_DOMAIN), - 'falseText' => __('Disabled', JTLWCC_TEXT_DOMAIN), + 'trueText' => __('Enabled', 'woo-jtl-connector'), + 'falseText' => __('Disabled', 'woo-jtl-connector'), ]; //Add prefix for delivery time textinput field $fields[] = [ - 'title' => __('Prefix for delivery time', JTLWCC_TEXT_DOMAIN), + 'title' => __('Prefix for delivery time', 'woo-jtl-connector'), 'type' => 'jtl_text_input', 'id' => Config::OPTIONS_PRAEFIX_DELIVERYTIME, 'value' => Config::get(Config::OPTIONS_PRAEFIX_DELIVERYTIME), - 'helpBlock' => __("Define the prefix like" . PHP_EOL . "'ca. 4 Days'.", JTLWCC_TEXT_DOMAIN), + 'helpBlock' => __("Define the prefix like\n'ca. 4 Days'.", 'woo-jtl-connector'), ]; //Add suffix for delivery time textinput field $fields[] = [ - 'title' => __('Suffix for delivery time', JTLWCC_TEXT_DOMAIN), + 'title' => __('Suffix for delivery time', 'woo-jtl-connector'), 'type' => 'jtl_text_input', 'id' => Config::OPTIONS_SUFFIX_DELIVERYTIME, 'value' => Config::get(Config::OPTIONS_SUFFIX_DELIVERYTIME), - 'helpBlock' => __("Define the Suffix like" . PHP_EOL . "'ca. 4 work days'.", JTLWCC_TEXT_DOMAIN), + 'helpBlock' => __("Define the Suffix like\n'ca. 4 work days'.", 'woo-jtl-connector'), ]; //Use next available inflow date if needed $fields[] = [ - 'title' => __('Consider available inflow date for shipping', JTLWCC_TEXT_DOMAIN), + 'title' => __('Consider available inflow date for shipping', 'woo-jtl-connector'), 'type' => 'active_true_false_radio', 'desc' => __( 'Enable if you want that connector calculate shipping time based on next a vailable inflow date from supplier when stock is 0', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), 'id' => Config::OPTIONS_CONSIDER_SUPPLIER_INFLOW_DATE, 'value' => Config::get(Config::OPTIONS_CONSIDER_SUPPLIER_INFLOW_DATE), - 'trueText' => __('Enabled', JTLWCC_TEXT_DOMAIN), - 'falseText' => __('Disabled', JTLWCC_TEXT_DOMAIN), + 'trueText' => __('Enabled', 'woo-jtl-connector'), + 'falseText' => __('Disabled', 'woo-jtl-connector'), ]; //Add sectionend @@ -1354,7 +1363,7 @@ private static function getCustomerOrderFields(): array 'With JTL-Connector for WooCommerce, you can connect your WooCommerce online shop with the free JTL-Wawi ERP system by JTL-Software. Customer order related settings of the installed JTL-Connector. Here you can set some options to modify the import of customer orders.', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), ]; @@ -1367,27 +1376,27 @@ private static function getCustomerOrderFields(): array //Add pull order since date field $fields[] = [ - 'title' => __('Pull orders since', JTLWCC_TEXT_DOMAIN), + 'title' => __('Pull orders since', 'woo-jtl-connector'), 'type' => 'jtl_date_field', // 'default' => '2019-03-22', 'value' => Config::get(Config::OPTIONS_PULL_ORDERS_SINCE), - 'helpBlock' => __('Define a start date for pulling of orders.', JTLWCC_TEXT_DOMAIN), + 'helpBlock' => __('Define a start date for pulling of orders.', 'woo-jtl-connector'), 'id' => Config::OPTIONS_PULL_ORDERS_SINCE, ]; $fields[] = [ - 'title' => __('Recalculate order when has coupons', JTLWCC_TEXT_DOMAIN), + 'title' => __('Recalculate order when has coupons', 'woo-jtl-connector'), 'type' => 'active_true_false_radio', 'desc' => __( 'When option is enabled, connector will recalculate order when coupons were applied to order.', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), 'id' => Config::OPTIONS_RECALCULATE_COUPONS_ON_PULL, 'value' => Config::get(Config::OPTIONS_RECALCULATE_COUPONS_ON_PULL), - 'trueText' => __('Enabled', JTLWCC_TEXT_DOMAIN), - 'falseText' => __('Disabled', JTLWCC_TEXT_DOMAIN), + 'trueText' => __('Enabled', 'woo-jtl-connector'), + 'falseText' => __('Disabled', 'woo-jtl-connector'), ]; $fields[] = [ - 'title' => __('Default order statuses to import', JTLWCC_TEXT_DOMAIN), + 'title' => __('Default order statuses to import', 'woo-jtl-connector'), 'type' => 'jtl_connector_multiselect', 'options' => wc_get_order_statuses(), 'id' => Config::OPTIONS_DEFAULT_ORDER_STATUSES_TO_IMPORT, @@ -1397,7 +1406,7 @@ private static function getCustomerOrderFields(): array ), 'helpBlock' => __( 'Order statuses that should be imported. Default: pending, processing, on hold, completed', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), ]; @@ -1411,7 +1420,7 @@ private static function getCustomerOrderFields(): array 'title' => __( 'Import payments with following payment types only when order is completed (usually manual payment types)', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), 'type' => 'jtl_connector_multiselect', 'options' => $paymentGateways, @@ -1423,23 +1432,23 @@ private static function getCustomerOrderFields(): array ]; $fields[] = [ - 'title' => __('Delay time in seconds before order import'), + 'title' => __('Delay time in seconds before order import', 'woo-jtl-connector'), 'type' => 'jtl_number_input', 'value' => Config::get(Config::OPTIONS_IGNORE_ORDERS_YOUNGER_THAN), - 'helpBlock' => __('Define the delay time in seconds before new orders get imported.', JTLWCC_TEXT_DOMAIN), + 'helpBlock' => __('Define the delay time in seconds before new orders get imported.', 'woo-jtl-connector'), 'id' => Config::OPTIONS_IGNORE_ORDERS_YOUNGER_THAN, ]; //Add custom checkout fields input field if (SupportedPlugins::isActive(SupportedPlugins::PLUGIN_CHECKOUT_FIELD_EDITOR_FOR_WOOCOMMERCE)) { $fields[] = [ - 'title' => __('Custom Checkout Fields', JTLWCC_TEXT_DOMAIN), + 'title' => __('Custom Checkout Fields', 'woo-jtl-connector'), 'type' => 'jtl_text_input', 'id' => Config::OPTIONS_CUSTOM_CHECKOUT_FIELDS, 'value' => Config::get(Config::OPTIONS_CUSTOM_CHECKOUT_FIELDS), 'helpBlock' => __( "Define what custom fields should be imported to Wawi. Comma-separated.", - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), ]; } @@ -1466,10 +1475,7 @@ private static function getCustomersFields(): array $fields[] = [ 'type' => 'title', - 'desc' => __( - '', - JTLWCC_TEXT_DOMAIN - ), + 'desc' => '', ]; @@ -1478,7 +1484,7 @@ private static function getCustomersFields(): array ]; $fields[] = [ - 'title' => __('Limit Customer Pull', JTLWCC_TEXT_DOMAIN), + 'title' => __('Limit Customer Pull', 'woo-jtl-connector'), 'type' => 'jtl_connector_select', 'id' => Config::OPTIONS_LIMIT_CUSTOMER_QUERY_TYPE, 'value' => Config::get( @@ -1486,10 +1492,10 @@ private static function getCustomersFields(): array Config::JTLWCC_CONFIG_DEFAULTS[ Config::OPTIONS_LIMIT_CUSTOMER_QUERY_TYPE ] ), 'options' => [ - 'no_filter' => __('No Limit', JTLWCC_TEXT_DOMAIN), - 'last_imported_order' => __('Since last pulled Order ID', JTLWCC_TEXT_DOMAIN), - 'not_imported' => __('Only from not pulled Order', JTLWCC_TEXT_DOMAIN), - 'fixed_date' => __('Since fixed Date', JTLWCC_TEXT_DOMAIN), + 'no_filter' => __('No Limit', 'woo-jtl-connector'), + 'last_imported_order' => __('Since last pulled Order ID', 'woo-jtl-connector'), + 'not_imported' => __('Only from not pulled Order', 'woo-jtl-connector'), + 'fixed_date' => __('Since fixed Date', 'woo-jtl-connector'), ], 'helpBlock' => __( '"No Limit" will Pull all Users in the User Group "Customers" (with B2B Market, define Groups below), @@ -1505,7 +1511,7 @@ private static function getCustomersFields(): array Timeout Errors in JTL-WAWI.

Speeds decreases linearly with the number of Customers and/or Orders except for "Only from not pulled Order" which decreases exponentially.', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), ]; @@ -1531,7 +1537,7 @@ private static function getCustomersFields(): array } $fields[] = [ - 'title' => __('Customer Groups to Pull (only with no Limit)', JTLWCC_TEXT_DOMAIN), + 'title' => __('Customer Groups to Pull (only with no Limit)', 'woo-jtl-connector'), 'type' => 'jtl_connector_multiselect', 'options' => $roles, 'id' => Config::OPTIONS_PULL_CUSTOMER_GROUPS, @@ -1539,7 +1545,7 @@ private static function getCustomersFields(): array 'helpBlock' => __( 'Pull Customers with this Customer Groups, only respected if no Limit is defined.
Guests are always pulled. ', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), ]; @@ -1556,12 +1562,12 @@ private static function getCustomersFields(): array $fields[] = [ - 'title' => __('B2B-Market/WooCommerce default customer group', JTLWCC_TEXT_DOMAIN), + 'title' => __('B2B-Market/WooCommerce default customer group', 'woo-jtl-connector'), 'type' => 'jtl_connector_select', 'id' => Config::OPTIONS_DEFAULT_CUSTOMER_GROUP, 'value' => Config::get(Config::OPTIONS_DEFAULT_CUSTOMER_GROUP), 'options' => $options, - 'helpBlock' => __('Define which customer group is default.', JTLWCC_TEXT_DOMAIN), + 'helpBlock' => __('Define which customer group is default.', 'woo-jtl-connector'), ]; } @@ -1590,47 +1596,47 @@ private static function getDeveloperSettingsFields(): array with the free JTL-Wawi ERP system by JTL-Software. Developer related settings of the installed JTL-Connector. Here you can enable/disable/reset/download the developer logs of the jtl connector.', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), ]; //Add dev log radio field $fields[] = [ - 'title' => __('Dev-Logs', JTLWCC_TEXT_DOMAIN), + 'title' => __('Dev-Logs', 'woo-jtl-connector'), 'type' => 'active_true_false_radio', 'desc' => __( 'Enable JTL-Connector dev-logs for debugging (Default : Disabled).', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), 'id' => Config::OPTIONS_DEVELOPER_LOGGING, 'value' => Config::get(Config::OPTIONS_DEVELOPER_LOGGING), - 'trueText' => __('Enabled', JTLWCC_TEXT_DOMAIN), - 'falseText' => __('Disabled', JTLWCC_TEXT_DOMAIN), + 'trueText' => __('Enabled', 'woo-jtl-connector'), + 'falseText' => __('Disabled', 'woo-jtl-connector'), ]; //Add dev log buttons $fields[] = [ 'type' => 'dev_log_btn', - 'downloadText' => __('Download', JTLWCC_TEXT_DOMAIN), - 'clearLogsText' => __('Clear logs', JTLWCC_TEXT_DOMAIN), + 'downloadText' => __('Download', 'woo-jtl-connector'), + 'clearLogsText' => __('Clear logs', 'woo-jtl-connector'), ]; $fields[] = [ - 'title' => __('Recommend WooCommerce Settings', JTLWCC_TEXT_DOMAIN), + 'title' => __('Recommend WooCommerce Settings', 'woo-jtl-connector'), 'type' => 'active_true_false_radio', 'desc' => __( 'JTL-Wawi set automatically stable settings (Default : Enabled). Disable this at your own risk!', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), 'id' => Config::OPTIONS_AUTO_WOOCOMMERCE_OPTIONS, 'value' => Config::get(Config::OPTIONS_AUTO_WOOCOMMERCE_OPTIONS), - 'trueText' => __('Enabled', JTLWCC_TEXT_DOMAIN), - 'falseText' => __('Disabled', JTLWCC_TEXT_DOMAIN), + 'trueText' => __('Enabled', 'woo-jtl-connector'), + 'falseText' => __('Disabled', 'woo-jtl-connector'), ]; //phpcs:disable $fields[] = [ - 'title' => __('Important information', JTLWCC_TEXT_DOMAIN), + 'title' => __('Important information', 'woo-jtl-connector'), 'type' => 'jtlwcc_card', 'color' => 'border-info', 'text-color' => 'text-danger', @@ -1642,27 +1648,27 @@ private static function getDeveloperSettingsFields(): array
  • Display prices in the shop: "Including tax" (Dont change this!)
  • Display prices during cart and checkout: "Including tax" (Dont change this!)
  • ', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), ]; //phpcs:enable if (SupportedPlugins::isActive(SupportedPlugins::PLUGIN_GERMAN_MARKET)) { $fields[] = [ - 'title' => __('Recommend German Market Settings', JTLWCC_TEXT_DOMAIN), + 'title' => __('Recommend German Market Settings', 'woo-jtl-connector'), 'type' => 'active_true_false_radio', 'desc' => __( 'JTL-Wawi set automatically stable settings (Default : Enabled). Disable this at your own risk!', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), 'id' => Config::OPTIONS_AUTO_GERMAN_MARKET_OPTIONS, 'value' => Config::get(Config::OPTIONS_AUTO_GERMAN_MARKET_OPTIONS), - 'trueText' => __('Enabled', JTLWCC_TEXT_DOMAIN), - 'falseText' => __('Disabled', JTLWCC_TEXT_DOMAIN), + 'trueText' => __('Enabled', 'woo-jtl-connector'), + 'falseText' => __('Disabled', 'woo-jtl-connector'), ]; //phpcs:disable $fields[] = [ - 'title' => __('Important information', JTLWCC_TEXT_DOMAIN), + 'title' => __('Important information', 'woo-jtl-connector'), 'type' => 'jtlwcc_card', 'color' => 'border-info', 'text-color' => 'text-danger', @@ -1697,7 +1703,7 @@ private static function getDeveloperSettingsFields(): array
  • Global Options > Prorated Tax Calculation For Fees & Shipping Cost: "On" (Dont change this!)
  • Global Options > Gross Shipping Costs and Gross Fees: "Off"
  • ', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), ]; //phpcs:enable @@ -1706,19 +1712,19 @@ private static function getDeveloperSettingsFields(): array //CURRENT DISBALED THIS if (SupportedPlugins::isActive(SupportedPlugins::PLUGIN_B2B_MARKET)) { $fields[] = [ - 'title' => __('Recommend B2B Market Settings', JTLWCC_TEXT_DOMAIN), + 'title' => __('Recommend B2B Market Settings', 'woo-jtl-connector'), 'type' => 'active_true_false_radio', 'desc' => __( 'JTL-Wawi set automatically stable settings (Default : Enabled). Disable this at your own risk!', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ), 'id' => Config::OPTIONS_AUTO_B2B_MARKET_OPTIONS, 'value' => Config::get(Config::OPTIONS_AUTO_B2B_MARKET_OPTIONS), - 'trueText' => __('Enabled', JTLWCC_TEXT_DOMAIN), - 'falseText' => __('Disabled', JTLWCC_TEXT_DOMAIN), + 'trueText' => __('Enabled', 'woo-jtl-connector'), + 'falseText' => __('Disabled', 'woo-jtl-connector'), ]; // $fields[] = [ -// 'title' => __('Important information', JTLWCC_TEXT_DOMAIN), +// 'title' => __('Important information', 'woo-jtl-connector'), // 'type' => 'jtlwcc_card', // 'color' => 'border-info', // 'text-color' => 'text-info', @@ -1759,35 +1765,35 @@ private static function displayNanvigation(string $page): void } ?>" href="admin.php?page=woo-jtl-connector-information"> @@ -1829,7 +1835,7 @@ private static function update(Db $db): void if (!\is_string($installed_version)) { throw new \InvalidArgumentException( - "Expected installed_version to be a string, got " . gettype($installed_version) . "instead." + "Expected installed_version to be a string, got " . esc_html(gettype($installed_version)) . " instead." ); } @@ -1949,8 +1955,7 @@ private static function update(Db $db): void //hotfix case '1.8.2.4': //hotfix - $dropOldQuery = 'DROP TABLE IF EXISTS `%s`;'; - $wpdb->query(sprintf($dropOldQuery, $wpdb->prefix . 'jtl_connector_link_customer_group')); + $wpdb->query("DROP TABLE IF EXISTS `" . esc_sql($wpdb->prefix . LinkTableNames::CUSTOMER_GROUP) . "`"); self::createCustomerGroupLinkingTable(); // no break case '1.8.2.5': @@ -2124,7 +2129,7 @@ protected static function updateImageIdentities(Db $db): void foreach ($imageMapping as $relationType => $identityType) { $updateIdentityQuery = sprintf( - 'UPDATE `%sjtl_connector_link_image` SET `type` = %d WHERE `type` = %d', + 'UPDATE `%s' . LinkTableNames::IMAGE . '` SET `type` = %d WHERE `type` = %d', $db->getWpDb()->prefix, $relationType, $identityType @@ -2141,36 +2146,73 @@ private static function update_to_multi_linking(): void //phpcs:ignore { global $wpdb; - $query = - 'CREATE TABLE IF NOT EXISTS `%s` ( + $result = true; + $wpdb->query('START TRANSACTION'); + + /** @phpstan-ignore booleanAnd.leftAlwaysTrue */ + $result = $result && $wpdb->query("CREATE TABLE IF NOT EXISTS `" . esc_sql(LinkTableNames::CATEGORY) . "` ( `endpoint_id` varchar(255) NOT NULL, `host_id` INT(10) NOT NULL, PRIMARY KEY (`endpoint_id`, `host_id`), INDEX (`host_id`), INDEX (`endpoint_id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'; - - $result = true; - $wpdb->query('START TRANSACTION'); - - /** @phpstan-ignore booleanAnd.leftAlwaysTrue */ - $result = $result && $wpdb->query(sprintf($query, 'jtl_connector_link_category')); - $result = $result && $wpdb->query(sprintf($query, 'jtl_connector_link_customer')); - $result = $result && $wpdb->query(sprintf($query, 'jtl_connector_link_product')); - $result = $result && $wpdb->query(sprintf($query, 'jtl_connector_link_image')); - $result = $result && $wpdb->query(sprintf($query, 'jtl_connector_link_order')); - $result = $result && $wpdb->query(sprintf($query, 'jtl_connector_link_payment')); - $result = $result && $wpdb->query(sprintf($query, 'jtl_connector_link_crossselling')); + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"); + $result = $result && $wpdb->query("CREATE TABLE IF NOT EXISTS `" . esc_sql(LinkTableNames::CUSTOMER) . "` ( + `endpoint_id` varchar(255) NOT NULL, + `host_id` INT(10) NOT NULL, + PRIMARY KEY (`endpoint_id`, `host_id`), + INDEX (`host_id`), + INDEX (`endpoint_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"); + $result = $result && $wpdb->query("CREATE TABLE IF NOT EXISTS `" . esc_sql(LinkTableNames::PRODUCT) . "` ( + `endpoint_id` varchar(255) NOT NULL, + `host_id` INT(10) NOT NULL, + PRIMARY KEY (`endpoint_id`, `host_id`), + INDEX (`host_id`), + INDEX (`endpoint_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"); + $result = $result && $wpdb->query("CREATE TABLE IF NOT EXISTS `" . esc_sql(LinkTableNames::IMAGE) . "` ( + `endpoint_id` varchar(255) NOT NULL, + `host_id` INT(10) NOT NULL, + PRIMARY KEY (`endpoint_id`, `host_id`), + INDEX (`host_id`), + INDEX (`endpoint_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"); + $result = $result && $wpdb->query("CREATE TABLE IF NOT EXISTS `" . esc_sql(LinkTableNames::ORDER) . "` ( + `endpoint_id` varchar(255) NOT NULL, + `host_id` INT(10) NOT NULL, + PRIMARY KEY (`endpoint_id`, `host_id`), + INDEX (`host_id`), + INDEX (`endpoint_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"); + $result = $result && $wpdb->query("CREATE TABLE IF NOT EXISTS `" . esc_sql(LinkTableNames::PAYMENT) . "` ( + `endpoint_id` varchar(255) NOT NULL, + `host_id` INT(10) NOT NULL, + PRIMARY KEY (`endpoint_id`, `host_id`), + INDEX (`host_id`), + INDEX (`endpoint_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"); + $result = $result && $wpdb->query("CREATE TABLE IF NOT EXISTS `" . esc_sql(LinkTableNames::CROSSSELLING) . "` ( + `endpoint_id` varchar(255) NOT NULL, + `host_id` INT(10) NOT NULL, + PRIMARY KEY (`endpoint_id`, `host_id`), + INDEX (`host_id`), + INDEX (`endpoint_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"); $types = $wpdb->get_results('SELECT type FROM `jtl_connector_link` GROUP BY type'); foreach ($types as $type) { $type = (int) $type->type; $tableName = self::get_table_name($type); - $result = $result && $wpdb->query(" - INSERT INTO `{$tableName}` (`host_id`, `endpoint_id`) - SELECT `host_id`, `endpoint_id` FROM `jtl_connector_link` WHERE `type` = {$type} - "); + if ($tableName === null) { + continue; + } + $result = $result && $wpdb->query($wpdb->prepare( + "INSERT INTO `" . esc_sql($tableName) . "` (`host_id`, `endpoint_id`) + SELECT `host_id`, `endpoint_id` FROM `jtl_connector_link` WHERE `type` = %d", + $type + )); } if ($result) { @@ -2182,7 +2224,7 @@ private static function update_to_multi_linking(): void //phpcs:ignore add_action('admin_notices', function (): void { self::jtlwcc_show_wordpress_error(__( 'The linking table migration was not successful. Please use the forum for help.', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' )); }); } @@ -2197,19 +2239,19 @@ private static function get_table_name(int $type ): ?string//phpcs:ignore { switch ($type) { case IdentityType::CATEGORY: - return 'jtl_connector_link_category'; + return LinkTableNames::CATEGORY; case IdentityType::CUSTOMER: - return 'jtl_connector_link_customer'; + return LinkTableNames::CUSTOMER; case IdentityType::PRODUCT: - return 'jtl_connector_link_product'; + return LinkTableNames::PRODUCT; case 16: - return 'jtl_connector_link_image'; + return LinkTableNames::IMAGE; case IdentityType::CUSTOMER_ORDER: - return 'jtl_connector_link_order'; + return LinkTableNames::ORDER; case IdentityType::PAYMENT: - return 'jtl_connector_link_payment'; + return LinkTableNames::PAYMENT; case IdentityType::CROSS_SELLING: - return 'jtl_connector_link_crossselling'; + return LinkTableNames::CROSSSELLING; } return null; @@ -2225,43 +2267,48 @@ private static function update_multi_linking_endpoint_types(Db $db): void //phpc global $wpdb; // Modify varchar endpoint_id to integer - $modifyEndpointType = 'ALTER TABLE `%s` MODIFY `endpoint_id` BIGINT(20) unsigned'; - $wpdb->query(sprintf($modifyEndpointType, 'jtl_connector_link_order')); - $wpdb->query(sprintf($modifyEndpointType, 'jtl_connector_link_payment')); - $wpdb->query(sprintf($modifyEndpointType, 'jtl_connector_link_product')); - $wpdb->query(sprintf($modifyEndpointType, 'jtl_connector_link_crossselling')); - $wpdb->query(sprintf($modifyEndpointType, 'jtl_connector_link_category')); + $wpdb->query("ALTER TABLE `" . esc_sql(LinkTableNames::ORDER) . "` MODIFY `endpoint_id` BIGINT(20) unsigned"); + $wpdb->query("ALTER TABLE `" . esc_sql(LinkTableNames::PAYMENT) . "` MODIFY `endpoint_id` BIGINT(20) unsigned"); + $wpdb->query("ALTER TABLE `" . esc_sql(LinkTableNames::PRODUCT) . "` MODIFY `endpoint_id` BIGINT(20) unsigned"); + $wpdb->query( + "ALTER TABLE `" . esc_sql(LinkTableNames::CROSSSELLING) + . "` MODIFY `endpoint_id` BIGINT(20) unsigned" + ); + $wpdb->query( + "ALTER TABLE `" . esc_sql(LinkTableNames::CATEGORY) + . "` MODIFY `endpoint_id` BIGINT(20) unsigned" + ); // Add is_guest column for customers instead of using a prefix - $wpdb->query('ALTER TABLE `jtl_connector_link_customer` ADD COLUMN `is_guest` BIT'); - $wpdb->query(sprintf( - 'UPDATE `jtl_connector_link_customer` + $wpdb->query('ALTER TABLE `' . esc_sql(LinkTableNames::CUSTOMER) . '` ADD COLUMN `is_guest` BIT'); + $wpdb->query($wpdb->prepare( + 'UPDATE `' . esc_sql(LinkTableNames::CUSTOMER) . '` SET `is_guest` = 1 - WHERE `endpoint_id` LIKE "%s_%%"', - Id::GUEST_PREFIX + WHERE `endpoint_id` LIKE %s', + Id::GUEST_PREFIX . '_%' )); - $wpdb->query(sprintf( - 'UPDATE `jtl_connector_link_customer` + $wpdb->query($wpdb->prepare( + 'UPDATE `' . esc_sql(LinkTableNames::CUSTOMER) . '` SET `is_guest` = 0 - WHERE `endpoint_id` NOT LIKE "%s_%%"', - Id::GUEST_PREFIX + WHERE `endpoint_id` NOT LIKE %s', + Id::GUEST_PREFIX . '_%' )); // Add type column for images instead of using a prefix - $wpdb->query('ALTER TABLE `jtl_connector_link_image` ADD COLUMN `type` INT(4) unsigned'); - $updateImageLinkingTable = ' - UPDATE `jtl_connector_link_image` + $wpdb->query('ALTER TABLE `' . esc_sql(LinkTableNames::IMAGE) . '` ADD COLUMN `type` INT(4) unsigned'); + $wpdb->query($wpdb->prepare( + 'UPDATE `' . esc_sql(LinkTableNames::IMAGE) . '` SET `type` = %d, `endpoint_id` = SUBSTRING(`endpoint_id`, 3) - WHERE `endpoint_id` LIKE "%s_%%"'; - $wpdb->query(sprintf( - $updateImageLinkingTable, + WHERE `endpoint_id` LIKE %s', IdentityType::CATEGORY, - Id::CATEGORY_PREFIX + Id::CATEGORY_PREFIX . '_%' )); - $wpdb->query(sprintf( - $updateImageLinkingTable, + $wpdb->query($wpdb->prepare( + 'UPDATE `' . esc_sql(LinkTableNames::IMAGE) . '` + SET `type` = %d, `endpoint_id` = SUBSTRING(`endpoint_id`, 3) + WHERE `endpoint_id` LIKE %s', IdentityType::PRODUCT, - Id::PRODUCT_PREFIX + Id::PRODUCT_PREFIX . '_%' )); self::add_constraints_for_multi_linking_tables('jtl_connector_link_', $db); @@ -2276,49 +2323,56 @@ private static function add_specifc_linking_tables(Db $db): void //phpcs:ignore { $wpdb = $db->getWpDb(); - $query = ' - CREATE TABLE IF NOT EXISTS `%s` ( + $wpdb->query(" + CREATE TABLE IF NOT EXISTS `" . esc_sql(LinkTableNames::SPECIFIC) . "` ( `endpoint_id` BIGINT(20) unsigned NOT NULL, `host_id` INT(10) unsigned NOT NULL, PRIMARY KEY (`endpoint_id`, `host_id`), INDEX (`host_id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'; - - $wpdb->query(sprintf($query, 'jtl_connector_link_specific')); - $wpdb->query(sprintf($query, 'jtl_connector_link_specific_value')); + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"); + $wpdb->query(" + CREATE TABLE IF NOT EXISTS `" . esc_sql(LinkTableNames::SPECIFIC_VALUE) . "` ( + `endpoint_id` BIGINT(20) unsigned NOT NULL, + `host_id` INT(10) unsigned NOT NULL, + PRIMARY KEY (`endpoint_id`, `host_id`), + INDEX (`host_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"); - $engine = $wpdb->get_var(sprintf( + $engine = $wpdb->get_var($wpdb->prepare( "SELECT ENGINE FROM information_schema.TABLES - WHERE TABLE_NAME = '{$wpdb->posts}' AND TABLE_SCHEMA = '%s'", + WHERE TABLE_NAME = %s AND TABLE_SCHEMA = %s", + $wpdb->posts, DB_NAME )); if ($engine === 'InnoDB') { if ( ! $db->checkIfFKExists( - 'jtl_connector_link_category', + LinkTableNames::CATEGORY, 'jtl_connector_link_category_1' ) ) { $wpdb->query( - "ALTER TABLE `jtl_connector_link_category` - ADD CONSTRAINT `jtl_connector_link_category_1` FOREIGN KEY (`endpoint_id`) - REFERENCES `{$wpdb->terms}` (`term_id`) ON DELETE CASCADE ON UPDATE NO ACTION" + "ALTER TABLE `" . esc_sql(LinkTableNames::CATEGORY) . "` + ADD CONSTRAINT `jtl_connector_link_category_1` + FOREIGN KEY (`endpoint_id`) + REFERENCES `" . esc_sql($wpdb->terms) + . "` (`term_id`) ON DELETE CASCADE ON UPDATE NO ACTION" ); } $table = $wpdb->prefix . 'woocommerce_attribute_taxonomies'; if ( ! $db->checkIfFKExists( - 'jtl_connector_link_specific', + LinkTableNames::SPECIFIC, 'jtl_connector_link_specific_1' ) ) { $wpdb->query(" - ALTER TABLE `jtl_connector_link_specific` + ALTER TABLE `" . esc_sql(LinkTableNames::SPECIFIC) . "` ADD CONSTRAINT `jtl_connector_link_specific_1` FOREIGN KEY (`endpoint_id`) - REFERENCES `{$table}` (`attribute_id`) ON DELETE CASCADE ON UPDATE NO ACTION"); + REFERENCES `" . esc_sql($table) . "` (`attribute_id`) ON DELETE CASCADE ON UPDATE NO ACTION"); } } } @@ -2330,25 +2384,22 @@ private static function set_linking_table_name_prefix_correctly(): void //phpcs: { global $wpdb; - $query = 'RENAME TABLE %s TO %s%s;'; - $tables = [ 'jtl_connector_category_level', - 'jtl_connector_link_category', - 'jtl_connector_link_crossselling', - 'jtl_connector_link_customer', - 'jtl_connector_link_image', - 'jtl_connector_link_order', - 'jtl_connector_link_payment', - 'jtl_connector_link_product', - 'jtl_connector_link_shipping_class', - 'jtl_connector_link_specific', - 'jtl_connector_link_specific_value', + LinkTableNames::CATEGORY, + LinkTableNames::CROSSSELLING, + LinkTableNames::CUSTOMER, + LinkTableNames::IMAGE, + LinkTableNames::ORDER, + LinkTableNames::PAYMENT, + LinkTableNames::PRODUCT, + LinkTableNames::SHIPPING_CLASS, + LinkTableNames::SPECIFIC, + LinkTableNames::SPECIFIC_VALUE, 'jtl_connector_product_checksum', ]; foreach ($tables as $table) { - $sql = sprintf($query, $table, $wpdb->prefix, $table); - $wpdb->query($sql); + $wpdb->query("RENAME TABLE `" . esc_sql($table) . "` TO `" . esc_sql($wpdb->prefix . $table) . "`"); } } @@ -2461,7 +2512,7 @@ public static function jtlconnector_plugin_row_meta(array $links, string $file ) if (\str_contains($file, 'woo-jtl-connector.php')) { $url = esc_url('https://guide.jtl-software.de/jtl/Kategorie:JTL-Connector:WooCommerce'); $new_links = [ - '' . __('Documentation', JTLWCC_TEXT_DOMAIN) . '', + '' . __('Documentation', 'woo-jtl-connector') . '', ]; $links = array_merge($links, $new_links); } @@ -2477,7 +2528,7 @@ public static function settings_link(array $links = [] ): array //phpcs:ignore { $settings_link = '' . __( 'Settings', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' ) . ''; array_unshift($links, $settings_link); @@ -2494,24 +2545,33 @@ public static function settings_link(array $links = [] ): array //phpcs:ignore */ public static function jtl_date_field(array $field): void //phpcs:ignore { - $option_value = $field['default'];// get_option($field['id'], $field['default']); + $option_value = (string) $field['default']; + $fieldId = esc_attr((string) $field['id']); + $fieldTitle = esc_html((string) $field['title']); + $fieldValue = isset($field['value']) + && $field['value'] !== '' + ? esc_attr((string) $field['value']) + : esc_attr($option_value); ?>
    - +
    + value="" + id="" + name="">
    - - + +
    - +
    + class="btn btn-outline-success"> + class="btn btn-outline-danger">
    */ public static function jtl_text_input(array $field): void //phpcs:ignore { + $fieldId = esc_attr((string) $field['id']); + $fieldTitle = esc_html((string) $field['title']); + $fieldValue = esc_attr((string) $field['value']); ?>
    - + - - + +
    - + - - + +
    - - - + + + + > - +
    403]); + } + + check_admin_referer('settings_save_woo-jtl-connector'); + $settings = $_REQUEST; foreach ($settings as $key => $item) { @@ -3048,9 +3233,14 @@ public static function save(): void ) ); - $request = $_SERVER["HTTP_REFERER"]; + $request = wp_get_referer(); - wp_redirect($request, 301); + if ($request) { + wp_safe_redirect($request, 301); + } else { + wp_safe_redirect(admin_url('admin.php?page=woo-jtl-connector'), 301); + } + exit; } /** @@ -3058,19 +3248,20 @@ public static function save(): void */ public static function default_customer_group_not_updated(): void //phpcs:ignore { - $message = __( + $message = esc_html(__( 'The default customer is not set. Please update the B2B-Market default customer group in the JTL-Connector settings', - JTLWCC_TEXT_DOMAIN - ); - $message .= ': ' . strtolower(__( - 'Customer Settings', - JTLWCC_TEXT_DOMAIN - )) . ''; + 'woo-jtl-connector' + )); + $message .= ': ' + . esc_html(strtolower(__( + 'Customer Settings', + 'woo-jtl-connector' + ))) . ''; echo '

    - ' . $message . ' + ' . wp_kses_post($message) . '

    '; } @@ -3080,19 +3271,20 @@ public static function default_customer_group_not_updated(): void //phpcs:ignore */ public static function pull_customer_group_not_updated(): void //phpcs:ignore { - $message = __( + $message = esc_html(__( 'The pull customer groups are not set. Please update the B2B-Market customer pull groups in the JTL-Connector settings', - JTLWCC_TEXT_DOMAIN - ); - $message .= ': ' . strtolower(__( - 'Customer Settings', - JTLWCC_TEXT_DOMAIN - )) . ''; + 'woo-jtl-connector' + )); + $message .= ': ' + . esc_html(strtolower(__( + 'Customer Settings', + 'woo-jtl-connector' + ))) . ''; echo '

    - ' . $message . ' + ' . wp_kses_post($message) . '

    '; } @@ -3104,7 +3296,7 @@ public function update_failed(): void //phpcs:ignore PSR1.Methods.CamelCapsMetho { self::jtlwcc_show_wordpress_error(__( 'The linking table migration was not successful. Please use the forum for help.', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' )); } @@ -3114,11 +3306,12 @@ public function update_failed(): void //phpcs:ignore PSR1.Methods.CamelCapsMetho public function directory_no_write_access(): void //phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps { self::jtlwcc_show_wordpress_error(sprintf( + // translators: %s: temporary directory path __( 'Directory %s has no write access.', - sys_get_temp_dir() + 'woo-jtl-connector' ), - JTLWCC_TEXT_DOMAIN + esc_html(sys_get_temp_dir()) )); } @@ -3127,7 +3320,7 @@ public function directory_no_write_access(): void //phpcs:ignore PSR1.Methods.Ca */ public function phar_extension(): void //phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps { - self::jtlwcc_show_wordpress_error(__('PHP extension "phar" could not be found.', JTLWCC_TEXT_DOMAIN)); + self::jtlwcc_show_wordpress_error(__('PHP extension "phar" could not be found.', 'woo-jtl-connector')); } /** @@ -3137,7 +3330,7 @@ public function suhosin_whitelist(): void //phpcs:ignore PSR1.Methods.CamelCapsM { self::jtlwcc_show_wordpress_error(__( 'PHP extension "phar" is not on the suhosin whitelist.', - JTLWCC_TEXT_DOMAIN + 'woo-jtl-connector' )); } // diff --git a/phpstan.neon b/phpstan.neon index 7838c7af..04c49367 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -10,4 +10,11 @@ parameters: - './var/*' ignoreErrors: - '#Constant WC_DELIMITER not found.#' - - '#PHPDoc tag @throws with type .* is not subtype of Throwable#' \ No newline at end of file + - + message: '#Binary operation "\."\s+between .+ and array\|string results in an error#' + path: includes/JtlConnectorAdmin.php + count: 7 + - + message: '#Strict comparison using === between non-empty-string and false will always evaluate to false#' + path: woo-jtl-connector.php + count: 1 \ No newline at end of file diff --git a/readme.txt b/readme.txt index 78fb2b05..e908ff36 100644 --- a/readme.txt +++ b/readme.txt @@ -1,12 +1,12 @@ === JTL-Connector for WooCommerce === -Contributors: papryk, ntbyk, platzkejtl +Contributors: papryk, ntbyk, platzkejtl, patrick-gugelsberger Tags: warenwirtschaft, jtl, connector, wms, erp -Requires at least: 4.7 -Tested up to: 6.4 -Requires PHP: 8.0 -WC requires at least: 3.4 -WC tested up to: 8.2 -Stable tag: 2.4.1 +Requires at least: 6.4 +Tested up to: 7.0 +Requires PHP: 8.3 +WC requires at least: 3.4.7 +WC tested up to: 10.8.1 +Stable tag: 2.4.2 License: GPLv3 License URI: http://www.gnu.org/licenses/lgpl-3.0.html @@ -152,6 +152,14 @@ This section describes how to install the plugin and get it working. == Changelog == += 2.4.2 = +* Security: Added capability checks and nonce verification to all admin endpoints (CVE-2026-9234) +* Security: Log download now streams via temporary file instead of writing to public directory +* Fix: Replaced external CDN resources with locally bundled assets +* Fix: Added proper SQL preparation and output escaping throughout the plugin +* Fix: Global functions now use jtlwcc_ prefix to avoid naming conflicts +* Fix: Replaced deprecated PHP functions with WordPress alternatives + Find the newest Changelog here: http://changelog.jtl-software.de/en/systems/connector/woocommerce == Upgrade Notice == diff --git a/src/Authentication/TokenValidator.php b/src/Authentication/TokenValidator.php index 2b4f7921..b15a42b3 100644 --- a/src/Authentication/TokenValidator.php +++ b/src/Authentication/TokenValidator.php @@ -24,6 +24,6 @@ public function __construct(string $endpointToken = '') */ public function validate(string $token): bool { - return $token === $this->endpointToken; + return \hash_equals($this->endpointToken, $token); } } diff --git a/src/Connector.php b/src/Connector.php index e14074e3..d649393e 100644 --- a/src/Connector.php +++ b/src/Connector.php @@ -2,8 +2,6 @@ namespace JtlWooCommerceConnector; -\ini_set('display_errors', 'off'); - use DI\Container; use DI\DependencyException; use DI\NotFoundException; @@ -97,7 +95,7 @@ public function initialize(ConfigInterface $config, Container $container, EventD if (!\is_string($featuresPath)) { throw new \InvalidArgumentException( - "Expected featuresPath to be string but got " . \gettype($featuresPath) . " instead" + "Expected featuresPath to be string but got " . \esc_html(\gettype($featuresPath)) . " instead" ); } diff --git a/src/Controllers/CategoryController.php b/src/Controllers/CategoryController.php index 47c96744..0e7d17f3 100755 --- a/src/Controllers/CategoryController.php +++ b/src/Controllers/CategoryController.php @@ -213,10 +213,10 @@ public function push(AbstractModel ...$models): array } else { $categoryTerm = \get_term($categoryId, CategoryUtil::TERM_TAXONOMY); if (!$categoryTerm instanceof \WP_Term) { - throw new \Exception(\sprintf("Cannot find category %s", $categoryId)); + throw new \Exception(\sprintf("Cannot find category %s", \esc_html((string)$categoryId))); } // WordPress does not create a unique slug itself if the given already exists - if ($categoryTerm->slug !== wc_strtolower($categoryData['slug'])) { + if ($categoryTerm->slug !== \wc_strtolower($categoryData['slug'])) { $categoryData['slug'] = \wp_unique_term_slug($categoryData['slug'], (object)$categoryData); } diff --git a/src/Controllers/CrossSellingController.php b/src/Controllers/CrossSellingController.php index 1231970e..3a9c8405 100644 --- a/src/Controllers/CrossSellingController.php +++ b/src/Controllers/CrossSellingController.php @@ -29,8 +29,8 @@ class CrossSellingController extends AbstractBaseController implements DeleteInterface, StatisticInterface { - public const CROSSSELLING_META_KEY = '_crosssell_ids'; - public const UPSELLING_META_KEY = '_upsell_ids'; + public const string CROSSSELLING_META_KEY = '_crosssell_ids'; + public const string UPSELLING_META_KEY = '_upsell_ids'; /** * @param QueryFilter $query diff --git a/src/Controllers/CustomerOrderController.php b/src/Controllers/CustomerOrderController.php index d3588885..435b3678 100644 --- a/src/Controllers/CustomerOrderController.php +++ b/src/Controllers/CustomerOrderController.php @@ -26,7 +26,7 @@ class CustomerOrderController extends AbstractBaseController implements PullInterface, StatisticInterface { /** Order received (unpaid) */ - public const + public const string STATUS_PENDING = 'pending', /** Payment received – the order is awaiting fulfillment */ STATUS_PROCESSING = 'processing', @@ -41,8 +41,8 @@ class CustomerOrderController extends AbstractBaseController implements PullInte /** Already paid */ STATUS_REFUNDED = 'refunded'; - public const BILLING_ID_PREFIX = 'b_'; - public const SHIPPING_ID_PREFIX = 's_'; + public const string BILLING_ID_PREFIX = 'b_'; + public const string SHIPPING_ID_PREFIX = 's_'; /** * @param QueryFilter $query @@ -178,7 +178,7 @@ protected function setPayPalPlusPaymentInfo(\WC_Order $order, CustomerOrderModel if (!\is_array($payPalPlusSettings)) { throw new \InvalidArgumentException( - "payPalSettings expected to be an array but got " . \gettype($payPalPlusSettings) + "payPalSettings expected to be an array but got " . \esc_html(\gettype($payPalPlusSettings)) ); } @@ -188,7 +188,7 @@ protected function setPayPalPlusPaymentInfo(\WC_Order $order, CustomerOrderModel if (!\is_array($orderMetaData)) { throw new \InvalidArgumentException( - "orderMetaData expected to be an array but got " . \gettype($orderMetaData) + "orderMetaData expected to be an array but got " . \esc_html(\gettype($orderMetaData)) ); } diff --git a/src/Controllers/GlobalData/CurrencyController.php b/src/Controllers/GlobalData/CurrencyController.php index 3b1144a6..df7ed637 100644 --- a/src/Controllers/GlobalData/CurrencyController.php +++ b/src/Controllers/GlobalData/CurrencyController.php @@ -13,10 +13,10 @@ class CurrencyController extends AbstractBaseController { - public const ISO = 'woocommerce_currency'; - public const SIGN_POSITION = 'woocommerce_currency_pos'; - public const CENT_DELIMITER = 'woocommerce_price_decimal_sep'; - public const THOUSAND_DELIMITER = 'woocommerce_price_thousand_sep'; + public const string ISO = 'woocommerce_currency'; + public const string SIGN_POSITION = 'woocommerce_currency_pos'; + public const string CENT_DELIMITER = 'woocommerce_price_decimal_sep'; + public const string THOUSAND_DELIMITER = 'woocommerce_price_thousand_sep'; /** * @return CurrencyModel[] diff --git a/src/Controllers/GlobalData/CustomerGroupController.php b/src/Controllers/GlobalData/CustomerGroupController.php index 05c6c0db..69c04c1f 100644 --- a/src/Controllers/GlobalData/CustomerGroupController.php +++ b/src/Controllers/GlobalData/CustomerGroupController.php @@ -16,7 +16,7 @@ class CustomerGroupController extends AbstractBaseController { - public const DEFAULT_GROUP = 'customer'; + public const string DEFAULT_GROUP = 'customer'; protected Db $db; protected Util $util; @@ -42,6 +42,7 @@ public function pull(): array ->setIsDefault(true); $defaultI18n = (new CustomerGroupI18n()) + // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- Third-party string from WooCommerce ->setName(\__('Customer', 'woocommerce')) ->setLanguageISO($langIso); @@ -71,7 +72,7 @@ public function pull(): array if (!\is_array($meta)) { throw new \InvalidArgumentException( - "meta expected to be an array but got " . \gettype($meta) . " instead" + "meta expected to be an array but got " . \esc_html(\gettype($meta)) . " instead" ); } diff --git a/src/Controllers/GlobalData/ShippingClassController.php b/src/Controllers/GlobalData/ShippingClassController.php index 8bdab1b0..d819e977 100644 --- a/src/Controllers/GlobalData/ShippingClassController.php +++ b/src/Controllers/GlobalData/ShippingClassController.php @@ -12,7 +12,7 @@ class ShippingClassController extends AbstractBaseController { - public const TERM_TAXONOMY = 'product_shipping_class'; + public const string TERM_TAXONOMY = 'product_shipping_class'; /** * @return array diff --git a/src/Controllers/GlobalDataController.php b/src/Controllers/GlobalDataController.php index 1fafc710..df856a59 100644 --- a/src/Controllers/GlobalDataController.php +++ b/src/Controllers/GlobalDataController.php @@ -52,11 +52,10 @@ public function pull(QueryFilter $query): array } if ($hasDefaultCustomerGroup === false) { - throw new Exception(\__( - "The default customer is not set. Please update the B2B-Market default customer group " - . "in the JTL-Connector settings in the Wordpress admin panel.", - \JTLWCC_TEXT_DOMAIN - )); + throw new Exception(\esc_html(\__( + 'The default customer is not set. Please update the B2B-Market default customer group in the JTL-Connector settings in the Wordpress admin panel.', // phpcs:ignore Generic.Files.LineLength.TooLong + 'woo-jtl-connector' + ))); } // if (Config::get(Config::OPTIONS_AUTO_WOOCOMMERCE_OPTIONS)) { diff --git a/src/Controllers/ImageController.php b/src/Controllers/ImageController.php index 9782bbc1..5cf0e127 100644 --- a/src/Controllers/ImageController.php +++ b/src/Controllers/ImageController.php @@ -40,14 +40,14 @@ class ImageController extends AbstractBaseController implements PushInterface, DeleteInterface { - public const GALLERY_DIVIDER = ','; - public const PRODUCT_THUMBNAIL = '_thumbnail_id'; - public const CATEGORY_THUMBNAIL = 'thumbnail_id'; - public const GALLERY_KEY = '_product_image_gallery'; - public const MANUFACTURER_KEY = 'pwb_brand_image'; - public const PRODUCT_IMAGE = 'product'; - public const CATEGORY_IMAGE = 'category'; - public const MANUFACTURER_IMAGE = 'manufacturer'; + public const string GALLERY_DIVIDER = ','; + public const string PRODUCT_THUMBNAIL = '_thumbnail_id'; + public const string CATEGORY_THUMBNAIL = 'thumbnail_id'; + public const string GALLERY_KEY = '_product_image_gallery'; + public const string MANUFACTURER_KEY = 'pwb_brand_image'; + public const string PRODUCT_IMAGE = 'product'; + public const string CATEGORY_IMAGE = 'category'; + public const string MANUFACTURER_IMAGE = 'manufacturer'; /** @var array */ private array $alreadyLinked = []; @@ -139,7 +139,7 @@ private function addNextImages(array $images, int $type, int $limit): array $model = new ManufacturerImage(); break; default: - throw new Exception(\sprintf("Invalid image type '%s'", $type)); + throw new Exception(\sprintf("Invalid image type '%s'", \esc_html((string)$type))); } $model->setId(new Identity($imageLinkId)) @@ -208,7 +208,7 @@ private function productImagePull(?int $limit = null): array if (!\is_int($postId)) { throw new \InvalidArgumentException( - "Expected postId to be an integer but got " . \gettype($postId) . " instead." + "Expected postId to be an integer but got " . \esc_html(\gettype($postId)) . " instead." ); } @@ -398,7 +398,7 @@ private function categoryImagePullByQuery(string $query): array if (!\is_array($images)) { throw new \InvalidArgumentException( - "Expected images to be an array but got " . \gettype($images) . " instead." + "Expected images to be an array but got " . \esc_html(\gettype($images)) . " instead." ); } @@ -697,7 +697,10 @@ protected function relinkImage(int $newEndpointId, AbstractImage $image): void $type = IdentityType::CATEGORY_IMAGE; break; default: - throw new \Exception(\sprintf('Relation type %s is not supported.', $image->getRelationType())); + throw new \Exception(\sprintf( + 'Relation type %s is not supported.', + \esc_html($image->getRelationType()) + )); } $primaryKeyMapper->delete( @@ -982,8 +985,8 @@ private function deleteImageTermMeta(AbstractImage $image, bool $realDelete): vo throw new RuntimeException( \sprintf( "Invalid relation %s type for id %s when deleting image.", - $image->getRelationType(), - $endpointId + \esc_html($image->getRelationType()), + \esc_html($endpointId) ) ); } diff --git a/src/Controllers/ManufacturerController.php b/src/Controllers/ManufacturerController.php index 00836fc4..1c3e7b04 100755 --- a/src/Controllers/ManufacturerController.php +++ b/src/Controllers/ManufacturerController.php @@ -31,8 +31,8 @@ class ManufacturerController extends AbstractBaseController implements DeleteInterface, StatisticInterface { - public const TAXONOMY_PERFECT_BRANDS = 'pwb-brand'; - public const TAXONOMY_GERMANIZED = 'product_manufacturer'; + public const string TAXONOMY_PERFECT_BRANDS = 'pwb-brand'; + public const string TAXONOMY_GERMANIZED = 'product_manufacturer'; /** @var array */ private static array $idCache = []; diff --git a/src/Controllers/Order/CustomerOrderItemController.php b/src/Controllers/Order/CustomerOrderItemController.php index d50c19a1..d3a83d1a 100644 --- a/src/Controllers/Order/CustomerOrderItemController.php +++ b/src/Controllers/Order/CustomerOrderItemController.php @@ -18,7 +18,7 @@ class CustomerOrderItemController extends AbstractBaseController { - public const PRICE_DECIMALS = 4; + public const int PRICE_DECIMALS = 4; /** @var array $taxRateCache Map tax rate id to tax rate */ protected static array $taxRateCache = []; diff --git a/src/Controllers/PaymentController.php b/src/Controllers/PaymentController.php index f16aec7f..1232adce 100644 --- a/src/Controllers/PaymentController.php +++ b/src/Controllers/PaymentController.php @@ -15,6 +15,7 @@ use Jtl\Connector\Core\Model\Payment as PaymentModel; use Jtl\Connector\Core\Model\QueryFilter; use Jtl\Connector\Core\Definition\PaymentType; +use JtlWooCommerceConnector\Utilities\LinkTableNames; use JtlWooCommerceConnector\Utilities\SqlHelper; use JtlWooCommerceConnector\Utilities\Util; use Psr\Log\InvalidArgumentException; @@ -26,7 +27,7 @@ */ class PaymentController extends AbstractBaseController implements PullInterface, PushInterface, StatisticInterface { - public const PAY_UPON_INVOICE = 'PAY_UPON_INVOICE'; + public const string PAY_UPON_INVOICE = 'PAY_UPON_INVOICE'; /** * @param QueryFilter $query @@ -78,11 +79,11 @@ public function pull(QueryFilter $query): array */ public function getOrderHostId(int $endpointId): int { - $query = \sprintf( - "Select host_id from %sjtl_connector_link_order where endpoint_id = %s", - $this->db->getWpDb()->prefix, - $endpointId - ); + /** @var string $table */ + $table = \esc_sql($this->db->getWpDb()->prefix . LinkTableNames::ORDER); + /** @var literal-string $sql */ + $sql = "Select host_id from `{$table}` where endpoint_id = %d"; + $query = (string)$this->db->getWpDb()->prepare($sql, $endpointId); return (int)$this->db->queryOne($query); } diff --git a/src/Controllers/Product/ProductAttrController.php b/src/Controllers/Product/ProductAttrController.php index d37e3622..4dcd6fe0 100644 --- a/src/Controllers/Product/ProductAttrController.php +++ b/src/Controllers/Product/ProductAttrController.php @@ -18,7 +18,7 @@ class ProductAttrController extends AbstractBaseController { - public const + public const string VISIBILITY_HIDDEN = 'hidden', VISIBILITY_CATALOG = 'catalog', VISIBILITY_SEARCH = 'search', diff --git a/src/Controllers/Product/ProductGermanMarketFieldsController.php b/src/Controllers/Product/ProductGermanMarketFieldsController.php index a8d6122d..f8f4c5cc 100644 --- a/src/Controllers/Product/ProductGermanMarketFieldsController.php +++ b/src/Controllers/Product/ProductGermanMarketFieldsController.php @@ -238,7 +238,7 @@ private function updateGermanMarketPPU(ProductModel $product): void $wcProduct = \wc_get_product($productId); if (!$wcProduct instanceof WC_Product) { - throw new \InvalidArgumentException("Product with ID {$productId} not found"); + throw new \InvalidArgumentException("Product with ID " . \esc_html($productId) . " not found"); } $metaData = $this->getGermanMarketMeta($wcProduct, $metaKeys); @@ -369,7 +369,7 @@ private function clearPPU(ProductModel $product, array $metaKeys): void $wcProduct = \wc_get_product($productId); if (!$wcProduct instanceof WC_Product) { - throw new \InvalidArgumentException("Product with ID {$productId} not found"); + throw new \InvalidArgumentException(\esc_html("Product with ID {$productId} not found")); } $metaData = $this->getGermanMarketMeta( diff --git a/src/Controllers/Product/ProductI18nController.php b/src/Controllers/Product/ProductI18nController.php index 35c078f8..435066e8 100755 --- a/src/Controllers/Product/ProductI18nController.php +++ b/src/Controllers/Product/ProductI18nController.php @@ -94,7 +94,7 @@ private function name(WC_Product $product): string if (!$parent instanceof \WC_Product) { throw new \InvalidArgumentException( - "Parent with ID {$product->get_parent_id()} not found." + "Parent with ID " . \esc_html((string)$product->get_parent_id()) . " not found." ); } @@ -104,7 +104,7 @@ private function name(WC_Product $product): string if (!$parent instanceof \WC_Product) { throw new \InvalidArgumentException( - "Parent with ID {$product->get_parent_id()} not found." + "Parent with ID " . \esc_html((string)$product->get_parent_id()) . " not found." ); } diff --git a/src/Controllers/Product/ProductManufacturerController.php b/src/Controllers/Product/ProductManufacturerController.php index b84b5981..ef9ce946 100644 --- a/src/Controllers/Product/ProductManufacturerController.php +++ b/src/Controllers/Product/ProductManufacturerController.php @@ -71,7 +71,7 @@ public function pullData(ProductModel $model): ?Identity if (!\is_array($terms)) { throw new \InvalidArgumentException( - 'Array type expected. Got ' . \gettype($terms) . ' instead.' + 'Array type expected. Got ' . \esc_html(\gettype($terms)) . ' instead.' ); } diff --git a/src/Controllers/Product/ProductPrice.php b/src/Controllers/Product/ProductPrice.php index 7ba6aebb..7d64af71 100644 --- a/src/Controllers/Product/ProductPrice.php +++ b/src/Controllers/Product/ProductPrice.php @@ -20,7 +20,7 @@ class ProductPrice extends AbstractBaseController { - public const GUEST_CUSTOMER_GROUP = 'wc_guest_customer_group'; + public const string GUEST_CUSTOMER_GROUP = 'wc_guest_customer_group'; /** * @param WC_Product $product diff --git a/src/Controllers/Product/ProductVaSpeAttrHandlerController.php b/src/Controllers/Product/ProductVaSpeAttrHandlerController.php index a6c2b72f..f4c3ee22 100644 --- a/src/Controllers/Product/ProductVaSpeAttrHandlerController.php +++ b/src/Controllers/Product/ProductVaSpeAttrHandlerController.php @@ -33,7 +33,7 @@ class ProductVaSpeAttrHandlerController extends AbstractBaseController { - public const + public const string PRODUCT_TYPE_ATTR = 'wc_product_type', DELIVERY_TIME_ATTR = 'wc_dt_offset', DOWNLOADABLE_ATTR = 'wc_downloadable', diff --git a/src/Controllers/Product/ProductVariationController.php b/src/Controllers/Product/ProductVariationController.php index 6300c3c1..090e7388 100644 --- a/src/Controllers/Product/ProductVariationController.php +++ b/src/Controllers/Product/ProductVariationController.php @@ -93,7 +93,7 @@ public function pullDataChild(WC_Product $product, ProductModel $model, string $ if (!$parentProduct instanceof \WC_Product) { throw new \InvalidArgumentException( - "Parent product with ID {$product->get_parent_id()} not found." + "Parent product with ID " . \esc_html((string)$product->get_parent_id()) . " not found." ); } diff --git a/src/Controllers/ProductController.php b/src/Controllers/ProductController.php index b5fabcee..51770639 100755 --- a/src/Controllers/ProductController.php +++ b/src/Controllers/ProductController.php @@ -56,7 +56,7 @@ class ProductController extends AbstractBaseController implements { use WawiProductPriceSchmuddelTrait; - public const + public const string TYPE_PARENT = 'parent', TYPE_CHILD = 'child', TYPE_SINGLE = 'single'; @@ -124,7 +124,7 @@ public function pull(QueryFilter $query): array ->setProductTypeId(new Identity($product->get_type())) ->setKeywords( ($tags = \wc_get_product_tag_list($product->get_id(), ' ')) - ? \strip_tags($tags) : '' + ? \wp_strip_all_tags($tags) : '' ) ->setCreationDate($postDate) ->setModified($modDate) @@ -750,8 +750,13 @@ public function updateVariationCombinationChild( ): void { $productId = (int)$wcProduct->get_id(); - $productTitle = \esc_html(\get_the_title((int)$product->getMasterProductId()->getEndpoint())); - $variation_post_title = \sprintf(\__('Variation #%s of %s', 'woocommerce'), $productId, $productTitle); + $productTitle = \esc_html(\get_the_title((int)$product->getMasterProductId()->getEndpoint())); + // translators: %1$s is the variation product ID, %2$s is the parent product title + $translatedFormat = \__( // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch + 'Variation #%1$s of %2$s', + 'woocommerce' + ); + $variation_post_title = \sprintf($translatedFormat, $productId, $productTitle); \wp_update_post([ 'ID' => $productId, 'post_title' => $variation_post_title, diff --git a/src/Controllers/StatusChangeController.php b/src/Controllers/StatusChangeController.php index 557b5648..a9503f74 100644 --- a/src/Controllers/StatusChangeController.php +++ b/src/Controllers/StatusChangeController.php @@ -10,6 +10,7 @@ use Jtl\Connector\Core\Model\CustomerOrder; use Jtl\Connector\Core\Model\Identity; use Jtl\Connector\Core\Model\StatusChange as StatusChangeModel; +use JtlWooCommerceConnector\Utilities\LinkTableNames; use Mockery\Exception; use PHPUnit\Framework\ExpectationFailedException; use Psr\Log\InvalidArgumentException; @@ -69,7 +70,7 @@ public function push(AbstractModel ...$models): array protected function linkIfPaymentIsNotLinked(StatusChangeModel $statusChange): void { global $wpdb; - $jclp = $wpdb->prefix . 'jtl_connector_link_payment'; + $jclp = $wpdb->prefix . LinkTableNames::PAYMENT; $customerOrderId = $statusChange->getCustomerOrderId(); $endpointId = $customerOrderId instanceof Identity ? $customerOrderId->getEndpoint() : "0"; diff --git a/src/Event/CanHandleEvent.php b/src/Event/CanHandleEvent.php index 8c6b792b..27a53e6b 100644 --- a/src/Event/CanHandleEvent.php +++ b/src/Event/CanHandleEvent.php @@ -8,7 +8,7 @@ class CanHandleEvent extends Event { - public const EVENT_NAME = 'connector.can_handle'; + public const string EVENT_NAME = 'connector.can_handle'; protected string $controller; protected string $action; diff --git a/src/Event/HandleDeleteEvent.php b/src/Event/HandleDeleteEvent.php index 2cdb6b4c..20cc5929 100644 --- a/src/Event/HandleDeleteEvent.php +++ b/src/Event/HandleDeleteEvent.php @@ -9,7 +9,7 @@ class HandleDeleteEvent extends Event { - public const EVENT_NAME = 'connector.handle.delete'; + public const string EVENT_NAME = 'connector.handle.delete'; protected mixed $result = null; protected string $controller; diff --git a/src/Event/HandlePullEvent.php b/src/Event/HandlePullEvent.php index 040d8422..445560fe 100644 --- a/src/Event/HandlePullEvent.php +++ b/src/Event/HandlePullEvent.php @@ -9,7 +9,7 @@ class HandlePullEvent extends Event { - public const EVENT_NAME = 'connector.handle.pull'; + public const string EVENT_NAME = 'connector.handle.pull'; protected mixed $result; protected string $controller; diff --git a/src/Event/HandlePushEvent.php b/src/Event/HandlePushEvent.php index 04fb5cc2..386e0db4 100644 --- a/src/Event/HandlePushEvent.php +++ b/src/Event/HandlePushEvent.php @@ -9,7 +9,7 @@ class HandlePushEvent extends Event { - public const EVENT_NAME = 'connector.handle.push'; + public const string EVENT_NAME = 'connector.handle.push'; protected mixed $result; protected string $controller; diff --git a/src/Event/HandleStatsEvent.php b/src/Event/HandleStatsEvent.php index 79ec3fa7..2dfe73f8 100644 --- a/src/Event/HandleStatsEvent.php +++ b/src/Event/HandleStatsEvent.php @@ -8,7 +8,7 @@ class HandleStatsEvent extends Event { - public const EVENT_NAME = 'connector.handle.stats'; + public const string EVENT_NAME = 'connector.handle.stats'; protected mixed $result; protected string $controller; diff --git a/src/Integrations/Plugins/AbstractPlugin.php b/src/Integrations/Plugins/AbstractPlugin.php index 07b0524c..b764011f 100644 --- a/src/Integrations/Plugins/AbstractPlugin.php +++ b/src/Integrations/Plugins/AbstractPlugin.php @@ -63,7 +63,11 @@ public function addComponents(ComponentInterface ...$components): PluginInterfac public function getComponent(string $name): ComponentInterface { if ($this->hasComponent($name) === false) { - throw new \Exception(\sprintf("Cannot find component %s in plugin %s", $name, $this->getName())); + throw new \Exception(\sprintf( + "Cannot find component %s in plugin %s", + \esc_html($name), + \esc_html($this->getName()) + )); } return $this->components[$name]; diff --git a/src/Integrations/Plugins/PerfectWooCommerceBrands/PerfectWooCommerceBrands.php b/src/Integrations/Plugins/PerfectWooCommerceBrands/PerfectWooCommerceBrands.php index 0f7b9bb7..10851442 100644 --- a/src/Integrations/Plugins/PerfectWooCommerceBrands/PerfectWooCommerceBrands.php +++ b/src/Integrations/Plugins/PerfectWooCommerceBrands/PerfectWooCommerceBrands.php @@ -24,7 +24,7 @@ */ class PerfectWooCommerceBrands extends AbstractPlugin { - public const TAXONOMY_PERFECT_BRANDS = 'pwb-brand'; + public const string TAXONOMY_PERFECT_BRANDS = 'pwb-brand'; /** * @return bool diff --git a/src/Integrations/Plugins/PluginsManager.php b/src/Integrations/Plugins/PluginsManager.php index a98d052d..e4d151ac 100644 --- a/src/Integrations/Plugins/PluginsManager.php +++ b/src/Integrations/Plugins/PluginsManager.php @@ -111,7 +111,7 @@ public function addPlugin(PluginInterface $plugin): self public function get(string $name): PluginInterface { if (!isset($this->pluginsList[$name])) { - throw new \Exception(\sprintf("Plugin %s not found in PluginsManager", $name)); + throw new \Exception(\sprintf("Plugin %s not found in PluginsManager", \esc_html($name))); } return $this->pluginsList[$name]; diff --git a/src/Integrations/Plugins/WooCommerce/WooCommerceProduct.php b/src/Integrations/Plugins/WooCommerce/WooCommerceProduct.php index b7743321..ba7ff4b6 100644 --- a/src/Integrations/Plugins/WooCommerce/WooCommerceProduct.php +++ b/src/Integrations/Plugins/WooCommerce/WooCommerceProduct.php @@ -98,7 +98,7 @@ private function getCreationDate(DateTime $creationDate, bool $gmt = false): str if (!$dateTimeInterval instanceof \DateInterval) { throw new \InvalidArgumentException( - 'Could not create DateInterval. Got ' . \gettype($dateTimeInterval) . ' instead.' + 'Could not create DateInterval. Got ' . \esc_html(\gettype($dateTimeInterval)) . ' instead.' ); } diff --git a/src/Integrations/Plugins/Wpml/WpmlCategory.php b/src/Integrations/Plugins/Wpml/WpmlCategory.php index efad1123..dfce84d3 100644 --- a/src/Integrations/Plugins/Wpml/WpmlCategory.php +++ b/src/Integrations/Plugins/Wpml/WpmlCategory.php @@ -13,6 +13,7 @@ use JtlWooCommerceConnector\Integrations\Plugins\WooCommerce\WooCommerceCategory; use JtlWooCommerceConnector\Integrations\Plugins\YoastSeo\YoastSeo; use JtlWooCommerceConnector\Utilities\Category as CategoryUtil; +use JtlWooCommerceConnector\Utilities\LinkTableNames; use JtlWooCommerceConnector\Utilities\SqlHelper; use Psr\Log\InvalidArgumentException; @@ -23,7 +24,7 @@ */ class WpmlCategory extends AbstractComponent { - public const + public const string PRODUCT_CATEGORY_TYPE = 'tax_product_cat'; /** @@ -131,7 +132,7 @@ public function getCategories(int $limit): array LEFT JOIN `%sjtl_connector_category_level` cl ON tt.term_taxonomy_id = cl.category_id LEFT JOIN - `%sjtl_connector_link_category` l ON t.term_id = l.endpoint_id + `%s' . LinkTableNames::CATEGORY . '` l ON t.term_id = l.endpoint_id LEFT JOIN `%sicl_translations` wpmlt ON tt.term_taxonomy_id = wpmlt.element_id WHERE @@ -173,7 +174,7 @@ public function getStats(): int LEFT JOIN `%sjtl_connector_category_level` cl ON tt.term_id = cl.category_id LEFT JOIN - `%sjtl_connector_link_category` l ON t.term_id = l.endpoint_id + `%s' . LinkTableNames::CATEGORY . '` l ON t.term_id = l.endpoint_id LEFT JOIN `%sicl_translations` wpmlt ON tt.term_taxonomy_id = wpmlt.element_id WHERE diff --git a/src/Integrations/Plugins/Wpml/WpmlGermanized.php b/src/Integrations/Plugins/Wpml/WpmlGermanized.php index 788fc189..d892f4aa 100644 --- a/src/Integrations/Plugins/Wpml/WpmlGermanized.php +++ b/src/Integrations/Plugins/Wpml/WpmlGermanized.php @@ -10,6 +10,7 @@ use JtlWooCommerceConnector\Integrations\Plugins\RankMathSeo\RankMathSeo; use JtlWooCommerceConnector\Integrations\Plugins\YoastSeo\YoastSeo; use JtlWooCommerceConnector\Logger\ErrorFormatter; +use JtlWooCommerceConnector\Utilities\LinkTableNames; use Psr\Log\InvalidArgumentException; /** @@ -30,7 +31,7 @@ public function getManufacturers(int $limit): array $wpmlPlugin = $this->getCurrentPlugin(); $wpdb = $wpmlPlugin->getWpDb(); - $jclm = $wpdb->prefix . 'jtl_connector_link_manufacturer'; + $jclm = $wpdb->prefix . LinkTableNames::MANUFACTURER; $translations = $wpdb->prefix . 'icl_translations'; $sql = \sprintf( @@ -84,7 +85,7 @@ public function saveTranslations(Manufacturer $jtlManufacturer): void if (!$manufacturerTerm instanceof \WP_Term) { throw new \InvalidArgumentException( - "Manufacturer with ID {$mainManufacturerId} not found." + "Manufacturer with ID " . \esc_html((string)$mainManufacturerId) . " not found." ); } @@ -175,7 +176,7 @@ public function getStats(): int /** @var Wpml $wpmlPlugin */ $wpmlPlugin = $this->getCurrentPlugin(); $wpdb = $wpmlPlugin->getWpDb(); - $jclm = $wpdb->prefix . 'jtl_connector_link_manufacturer'; + $jclm = $wpdb->prefix . LinkTableNames::MANUFACTURER; $sql = \sprintf( " diff --git a/src/Integrations/Plugins/Wpml/WpmlMedia.php b/src/Integrations/Plugins/Wpml/WpmlMedia.php index 690adaa1..a0d7ceeb 100644 --- a/src/Integrations/Plugins/Wpml/WpmlMedia.php +++ b/src/Integrations/Plugins/Wpml/WpmlMedia.php @@ -11,6 +11,7 @@ use JtlWooCommerceConnector\Integrations\Plugins\AbstractComponent; use JtlWooCommerceConnector\Utilities\Category as CategoryUtil; use JtlWooCommerceConnector\Utilities\Id; +use JtlWooCommerceConnector\Utilities\LinkTableNames; /** * Class WpmlMedia @@ -19,7 +20,7 @@ */ class WpmlMedia extends AbstractComponent { - public const + public const string ELEMENT_TYPE = 'post_attachment'; /** @@ -99,7 +100,7 @@ public function getImageProductThumbnailSql(): string /** @var Wpml $wpmlPlugin */ $wpmlPlugin = $this->getCurrentPlugin(); $wpdb = $wpmlPlugin->getWpDb(); - $jcli = $wpdb->prefix . 'jtl_connector_link_image'; + $jcli = $wpdb->prefix . LinkTableNames::IMAGE; $wpmlt = $wpdb->prefix . 'icl_translations'; $sql = \sprintf( @@ -208,7 +209,7 @@ protected function buildImageQueryPull( $wpdb = $wpmlPlugin->getWpDb(); $limitQuery = \is_null($limit) ? '' : 'LIMIT ' . $limit; - $jcli = $wpdb->prefix . 'jtl_connector_link_image'; + $jcli = $wpdb->prefix . LinkTableNames::IMAGE; $wpmlt = $wpdb->prefix . 'icl_translations'; $sql = \sprintf( diff --git a/src/Integrations/Plugins/Wpml/WpmlPerfectWooCommerceBrands.php b/src/Integrations/Plugins/Wpml/WpmlPerfectWooCommerceBrands.php index 3845e679..e9b4aaf7 100644 --- a/src/Integrations/Plugins/Wpml/WpmlPerfectWooCommerceBrands.php +++ b/src/Integrations/Plugins/Wpml/WpmlPerfectWooCommerceBrands.php @@ -10,6 +10,7 @@ use JtlWooCommerceConnector\Integrations\Plugins\RankMathSeo\RankMathSeo; use JtlWooCommerceConnector\Integrations\Plugins\YoastSeo\YoastSeo; use JtlWooCommerceConnector\Logger\ErrorFormatter; +use JtlWooCommerceConnector\Utilities\LinkTableNames; use Psr\Log\InvalidArgumentException; /** @@ -30,7 +31,7 @@ public function getManufacturers(int $limit): array $wpmlPlugin = $this->getCurrentPlugin(); $wpdb = $wpmlPlugin->getWpDb(); - $jclm = $wpdb->prefix . 'jtl_connector_link_manufacturer'; + $jclm = $wpdb->prefix . LinkTableNames::MANUFACTURER; $translations = $wpdb->prefix . 'icl_translations'; $sql = \sprintf( @@ -84,7 +85,7 @@ public function saveTranslations(Manufacturer $jtlManufacturer): void if (!$manufacturerTerm instanceof \WP_Term) { throw new \InvalidArgumentException( - "Manufacturer with ID {$mainManufacturerId} not found." + "Manufacturer with ID " . \esc_html((string)$mainManufacturerId) . " not found." ); } @@ -175,7 +176,7 @@ public function getStats(): int /** @var Wpml $wpmlPlugin */ $wpmlPlugin = $this->getCurrentPlugin(); $wpdb = $wpmlPlugin->getWpDb(); - $jclm = $wpdb->prefix . 'jtl_connector_link_manufacturer'; + $jclm = $wpdb->prefix . LinkTableNames::MANUFACTURER; $sql = \sprintf( " diff --git a/src/Integrations/Plugins/Wpml/WpmlProduct.php b/src/Integrations/Plugins/Wpml/WpmlProduct.php index 87fa5c83..84c6f6e6 100644 --- a/src/Integrations/Plugins/Wpml/WpmlProduct.php +++ b/src/Integrations/Plugins/Wpml/WpmlProduct.php @@ -17,6 +17,7 @@ use JtlWooCommerceConnector\Integrations\Plugins\AbstractComponent; use JtlWooCommerceConnector\Integrations\Plugins\WooCommerce\WooCommerce; use JtlWooCommerceConnector\Integrations\Plugins\WooCommerce\WooCommerceProduct; +use JtlWooCommerceConnector\Utilities\LinkTableNames; use JtlWooCommerceConnector\Utilities\Util; use stdClass; use WC_Product_Variation; @@ -28,7 +29,7 @@ */ class WpmlProduct extends AbstractComponent { - public const + public const string POST_TYPE = 'post_product', POST_TYPE_VARIATION = 'post_product_variation'; @@ -203,7 +204,9 @@ protected function saveTranslation( $wcProduct = \wc_get_product($wcProductId); if (!$wcProduct instanceof \WC_Product) { - throw new \InvalidArgumentException("Product with ID {$wcProductId} not found"); + throw new \InvalidArgumentException( + "Product with ID " . \esc_html((string)$wcProductId) . " not found" + ); } $wcProduct->set_parent_id($masterProductId); @@ -263,7 +266,7 @@ public function getProducts(?int $limit = null): array /** @var Wpml $wpmlPlugin */ $wpmlPlugin = $this->getCurrentPlugin(); $wpdb = $wpmlPlugin->getWpDb(); - $jclp = $wpdb->prefix . 'jtl_connector_link_product'; + $jclp = $wpdb->prefix . LinkTableNames::PRODUCT; $translations = $wpdb->prefix . 'icl_translations'; $defaultLanguage = $wpmlPlugin->getDefaultLanguage(); diff --git a/src/Integrations/Plugins/Wpml/WpmlSpecific.php b/src/Integrations/Plugins/Wpml/WpmlSpecific.php index 1a95185a..33949876 100644 --- a/src/Integrations/Plugins/Wpml/WpmlSpecific.php +++ b/src/Integrations/Plugins/Wpml/WpmlSpecific.php @@ -11,6 +11,7 @@ use JtlWooCommerceConnector\Integrations\Plugins\WooCommerce\WooCommerce; use JtlWooCommerceConnector\Integrations\Plugins\WooCommerce\WooCommerceSpecific; use JtlWooCommerceConnector\Integrations\Plugins\WooCommerce\WooCommerceSpecificValue; +use JtlWooCommerceConnector\Utilities\LinkTableNames; use JtlWooCommerceConnector\Utilities\Util; use Psr\Log\InvalidArgumentException; use WPML\Auryn\InjectionException; @@ -33,7 +34,7 @@ public function getStats(): int $wpdb = $wpmlPlugin->getWpDb(); $wat = $wpdb->prefix . 'woocommerce_attribute_taxonomies'; - $jcls = $wpdb->prefix . 'jtl_connector_link_specific'; + $jcls = $wpdb->prefix . LinkTableNames::SPECIFIC; $sql = \sprintf(" SELECT COUNT(at.attribute_id) @@ -129,7 +130,7 @@ public function getValues(string $specificName): ?array /** @var Wpml $wpmlPlugin */ $wpmlPlugin = $this->getCurrentPlugin(); $wpdb = $wpmlPlugin->getWpDb(); - $jclsv = $wpdb->prefix . 'jtl_connector_link_specific_value'; + $jclsv = $wpdb->prefix . LinkTableNames::SPECIFIC_VALUE; $iclt = $wpdb->prefix . 'icl_translations'; $languageCode = $wpmlPlugin->getDefaultLanguage(); $elementType = 'tax_' . \esc_sql($specificName); diff --git a/src/Integrations/Plugins/Wpml/WpmlSpecificValue.php b/src/Integrations/Plugins/Wpml/WpmlSpecificValue.php index 2c1cbf0e..c30cda46 100644 --- a/src/Integrations/Plugins/Wpml/WpmlSpecificValue.php +++ b/src/Integrations/Plugins/Wpml/WpmlSpecificValue.php @@ -204,7 +204,10 @@ public function setTranslations( if (!$translated_term) { $term_result = \wp_insert_term($specificValueI18n->getValue(), $taxonomy); if (\is_wp_error($term_result)) { - $this->logger->error("Error while creating term: " . $term_result->get_error_message()); + $this->logger->error( + "Error while creating term: " + . \esc_html($term_result->get_error_message()) + ); return; } $translated_term_id = $term_result['term_id']; diff --git a/src/Mapper/PrimaryKeyMapper.php b/src/Mapper/PrimaryKeyMapper.php index 62023f4d..3d4e139e 100644 --- a/src/Mapper/PrimaryKeyMapper.php +++ b/src/Mapper/PrimaryKeyMapper.php @@ -9,6 +9,7 @@ use Jtl\Connector\Core\Mapper\PrimaryKeyMapperInterface; use JtlWooCommerceConnector\Utilities\Db; use JtlWooCommerceConnector\Utilities\Id; +use JtlWooCommerceConnector\Utilities\LinkTableNames; use JtlWooCommerceConnector\Utilities\SqlHelper; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; @@ -249,21 +250,21 @@ public static function getTableName(int $type): ?string switch ($type) { case IdentityType::CATEGORY: - return 'jtl_connector_link_category'; + return LinkTableNames::CATEGORY; case IdentityType::CROSS_SELLING: - return 'jtl_connector_link_crossselling'; + return LinkTableNames::CROSSSELLING; case IdentityType::CROSS_SELLING_GROUP: - return 'jtl_connector_link_crossselling_group'; + return LinkTableNames::CROSSSELLING_GROUP; /* * case IdentityLinker::TYPE_CURRENCY: - * return 'jtl_connector_link_currency'; + * return LinkTableNames::CURRENCY; */ case IdentityType::CUSTOMER: - return 'jtl_connector_link_customer'; + return LinkTableNames::CUSTOMER; case IdentityType::CUSTOMER_GROUP: - return 'jtl_connector_link_customer_group'; + return LinkTableNames::CUSTOMER_GROUP; case IdentityType::CONFIG_GROUP_IMAGE: case IdentityType::PRODUCT_VARIATION_VALUE_IMAGE: case IdentityType::SPECIFIC_IMAGE: @@ -271,41 +272,41 @@ public static function getTableName(int $type): ?string case IdentityType::MANUFACTURER_IMAGE: case IdentityType::CATEGORY_IMAGE: case IdentityType::PRODUCT_IMAGE: - return 'jtl_connector_link_image'; + return LinkTableNames::IMAGE; /* * case IdentityLinker::TYPE_LANGUAGE: - * return 'jtl_connector_link_language'; + * return LinkTableNames::LANGUAGE; */ case IdentityType::MANUFACTURER: - return 'jtl_connector_link_manufacturer'; + return LinkTableNames::MANUFACTURER; /* * case IdentityLinker::TYPE_MEASUREMENT_UNIT: - * return 'jtl_connector_link_measurement_unit'; + * return LinkTableNames::MEASUREMENT_UNIT; */ case IdentityType::CUSTOMER_ORDER: - return 'jtl_connector_link_order'; + return LinkTableNames::ORDER; case IdentityType::PAYMENT: - return 'jtl_connector_link_payment'; + return LinkTableNames::PAYMENT; case IdentityType::PRODUCT: - return 'jtl_connector_link_product'; + return LinkTableNames::PRODUCT; case IdentityType::SHIPPING_CLASS: - return 'jtl_connector_link_shipping_class'; + return LinkTableNames::SHIPPING_CLASS; /* * case IdentityLinker::TYPE_SHIPPING_METHOD: - * return 'jtl_connector_link_shipping_method'; + * return LinkTableNames::SHIPPING_METHOD; */ case IdentityType::SPECIFIC: - return 'jtl_connector_link_specific'; + return LinkTableNames::SPECIFIC; case IdentityType::SPECIFIC_VALUE: - return 'jtl_connector_link_specific_value'; + return LinkTableNames::SPECIFIC_VALUE; case IdentityType::TAX_CLASS: - return 'jtl_connector_link_tax_class'; + return LinkTableNames::TAX_CLASS; } return null; diff --git a/src/Models/CrossSellingGroup.php b/src/Models/CrossSellingGroup.php index 1da85733..b1ecf227 100644 --- a/src/Models/CrossSellingGroup.php +++ b/src/Models/CrossSellingGroup.php @@ -16,8 +16,8 @@ */ class CrossSellingGroup { - public const TYPE_CROSS_SELL = "1"; - public const TYPE_UP_SELL = "2"; + public const string TYPE_CROSS_SELL = "1"; + public const string TYPE_UP_SELL = "2"; /** @var array> */ protected static array $groups = [ diff --git a/src/Utilities/Category.php b/src/Utilities/Category.php index 73e3d0d8..7f80fa21 100644 --- a/src/Utilities/Category.php +++ b/src/Utilities/Category.php @@ -9,9 +9,9 @@ class Category { - public const TERM_TAXONOMY = 'product_cat'; - public const LEVEL_TABLE = 'jtl_connector_category_level'; - public const OPTION_CATEGORY_HAS_CHANGED = 'jtlconnector_category_has_changes'; + public const string TERM_TAXONOMY = 'product_cat'; + public const string LEVEL_TABLE = 'jtl_connector_category_level'; + public const string OPTION_CATEGORY_HAS_CHANGED = 'jtlconnector_category_has_changes'; protected Db $db; diff --git a/src/Utilities/Config.php b/src/Utilities/Config.php index 791a07b1..9e7bcfbe 100644 --- a/src/Utilities/Config.php +++ b/src/Utilities/Config.php @@ -15,7 +15,7 @@ */ class Config { - public const + public const string OPTIONS_AUTO_B2B_MARKET_OPTIONS = 'jtlconnector_auto_b2b_market', OPTIONS_PULL_ORDERS_SINCE = 'jtlconnector_pull_orders_since', OPTIONS_SHOW_VARIATION_SPECIFICS_ON_PRODUCT_PAGE = 'jtlconnector_show_variation_specifics_on_product_page', @@ -46,7 +46,7 @@ class Config OPTIONS_FEATURES_JSON = 'jtlconnector_features_json', OPTIONS_IGNORE_ORDERS_YOUNGER_THAN = 'jtlconnector_ignore_orders_younger_than'; - public const JTLWCC_CONFIG_DEFAULTS = [ + public const array JTLWCC_CONFIG_DEFAULTS = [ //FIRSTPAGE Config::OPTIONS_SHOW_VARIATION_SPECIFICS_ON_PRODUCT_PAGE => true, Config::OPTIONS_SEND_CUSTOM_PROPERTIES => true, @@ -86,7 +86,7 @@ class Config Config::OPTIONS_AUTO_B2B_MARKET_OPTIONS => true, ]; - public const JTLWCC_CONFIG = [ + public const array JTLWCC_CONFIG = [ //FIRSTPAGE Config::OPTIONS_SHOW_VARIATION_SPECIFICS_ON_PRODUCT_PAGE => 'bool', Config::OPTIONS_DELETE_UNKNOWN_ATTRIBUTES => 'bool', @@ -183,7 +183,7 @@ public static function getBuildVersion(): string if (!\is_array($buildConfig)) { throw new \InvalidArgumentException( - "Expected buildConfig to be an array, got " . \gettype($buildConfig) + \esc_html("Expected buildConfig to be an array, got " . \gettype($buildConfig)) ); } @@ -210,7 +210,7 @@ public static function writeCoreConfigFile(string $key, bool $value): bool $config = new \stdClass(); if (!\file_exists($file)) { - \file_put_contents($file, \json_encode($config)); + \file_put_contents($file, \wp_json_encode($config)); } else { $config = \json_decode((string)\file_get_contents($file)); if (!$config instanceof \stdClass) { @@ -237,6 +237,6 @@ public static function writeCoreConfigFile(string $key, bool $value): bool $config->{$key} = $value; } - return (bool)\file_put_contents($file, \json_encode($config)); + return (bool)\file_put_contents($file, \wp_json_encode($config)); } } diff --git a/src/Utilities/Constants.php b/src/Utilities/Constants.php index b0880f96..21b269cd 100644 --- a/src/Utilities/Constants.php +++ b/src/Utilities/Constants.php @@ -7,13 +7,13 @@ class Constants { //CONN - public const CONNECTOR_NAMESPACE = "\\JtlWooCommerceConnector\\"; - public const CONNECTOR_TEST_NAMESPACE = "\\JtlWooCommerceConnector\\Tests\\"; - public const MAPPER_NAMESPACE = "\\JtlWooCommerceConnector\\Mapper\\"; - public const CONTROLLER_NAMESPACE = "\\JtlWooCommerceConnector\\Controllers\\"; - public const UTILITY_NAMESPACE = "\\JtlWooCommerceConnector\\Utilities\\"; + public const string CONNECTOR_NAMESPACE = "\\JtlWooCommerceConnector\\"; + public const string CONNECTOR_TEST_NAMESPACE = "\\JtlWooCommerceConnector\\Tests\\"; + public const string MAPPER_NAMESPACE = "\\JtlWooCommerceConnector\\Mapper\\"; + public const string CONTROLLER_NAMESPACE = "\\JtlWooCommerceConnector\\Controllers\\"; + public const string UTILITY_NAMESPACE = "\\JtlWooCommerceConnector\\Utilities\\"; //CORE - public const CORE_NAMESPACE = "\\jtl\\Connector\\"; - public const CORE_MODEL_NAMESPACE = "\\jtl\\Connector\\Model\\"; - public const CORE_TYPE_NAMESPACE = "\\jtl\\Connector\\Type\\"; + public const string CORE_NAMESPACE = "\\jtl\\Connector\\"; + public const string CORE_MODEL_NAMESPACE = "\\jtl\\Connector\\Model\\"; + public const string CORE_TYPE_NAMESPACE = "\\jtl\\Connector\\Type\\"; } diff --git a/src/Utilities/Date.php b/src/Utilities/Date.php index b406eab4..ffbf284b 100644 --- a/src/Utilities/Date.php +++ b/src/Utilities/Date.php @@ -6,12 +6,12 @@ class Date { - public const HOURS_PER_DAY = 24; - public const MINUTES_PER_HOUR = 60; - public const SECONDS_PER_MINUTE = 60; - public const LAST_HOUR = 23; - public const LAST_MINUTE = 59; - public const LAST_SECOND = 59; + public const int HOURS_PER_DAY = 24; + public const int MINUTES_PER_HOUR = 60; + public const int SECONDS_PER_MINUTE = 60; + public const int LAST_HOUR = 23; + public const int LAST_MINUTE = 59; + public const int LAST_SECOND = 59; /** * @param string $date diff --git a/src/Utilities/Db.php b/src/Utilities/Db.php index 8a32dfee..bd2f2fe5 100644 --- a/src/Utilities/Db.php +++ b/src/Utilities/Db.php @@ -127,12 +127,16 @@ public function getWpDb(): wpdb */ public function checkIfFKExists(string $table, string $constraint): bool { - $sql = " - SELECT COUNT(*) - FROM information_schema.TABLE_CONSTRAINTS - WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = '{$table}' - AND CONSTRAINT_NAME = '{$constraint}';"; + $wpdb = $this->getWpDb(); + $sql = (string) $wpdb->prepare( + "SELECT COUNT(*) + FROM information_schema.TABLE_CONSTRAINTS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = %s + AND CONSTRAINT_NAME = %s", + $table, + $constraint + ); $test = $this->queryOne($sql); return (bool)$test; diff --git a/src/Utilities/Germanized.php b/src/Utilities/Germanized.php index 44f81b47..804eaa6f 100644 --- a/src/Utilities/Germanized.php +++ b/src/Utilities/Germanized.php @@ -28,7 +28,9 @@ class Germanized public function __construct() { $this->salutations = [ + // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- Third-party string from WooCommerce Germanized 1 => \__('Mr.', 'woocommerce-germanized'),// m + // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- Third-party string from WooCommerce Germanized 2 => \__('Ms.', 'woocommerce-germanized') // f ]; } diff --git a/src/Utilities/Id.php b/src/Utilities/Id.php index a6ae8124..59a3d16a 100644 --- a/src/Utilities/Id.php +++ b/src/Utilities/Id.php @@ -8,11 +8,11 @@ class Id { - public const SEPARATOR = '_'; - public const PRODUCT_PREFIX = 'p'; - public const CATEGORY_PREFIX = 'c'; - public const GUEST_PREFIX = 'g'; - public const MANUFACTURER_PREFIX = 'm'; + public const string SEPARATOR = '_'; + public const string PRODUCT_PREFIX = 'p'; + public const string CATEGORY_PREFIX = 'c'; + public const string GUEST_PREFIX = 'g'; + public const string MANUFACTURER_PREFIX = 'm'; /** * @param array $endpointIds diff --git a/src/Utilities/LinkTableNames.php b/src/Utilities/LinkTableNames.php new file mode 100644 index 00000000..97c45d1a --- /dev/null +++ b/src/Utilities/LinkTableNames.php @@ -0,0 +1,34 @@ +prefix` at + * runtime to obtain the fully-qualified table name. + */ +final class LinkTableNames +{ + public const string CATEGORY = 'jtl_connector_link_category'; + public const string CROSSSELLING = 'jtl_connector_link_crossselling'; + public const string CROSSSELLING_GROUP = 'jtl_connector_link_crossselling_group'; + public const string CURRENCY = 'jtl_connector_link_currency'; + public const string CUSTOMER = 'jtl_connector_link_customer'; + public const string CUSTOMER_GROUP = 'jtl_connector_link_customer_group'; + public const string IMAGE = 'jtl_connector_link_image'; + public const string LANGUAGE = 'jtl_connector_link_language'; + public const string MANUFACTURER = 'jtl_connector_link_manufacturer'; + public const string MANUFACTURER_UNIT = 'jtl_connector_link_manufacturer_unit'; + public const string MEASUREMENT_UNIT = 'jtl_connector_link_measurement_unit'; + public const string ORDER = 'jtl_connector_link_order'; + public const string PAYMENT = 'jtl_connector_link_payment'; + public const string PRODUCT = 'jtl_connector_link_product'; + public const string SHIPPING_CLASS = 'jtl_connector_link_shipping_class'; + public const string SHIPPING_METHOD = 'jtl_connector_link_shipping_method'; + public const string SPECIFIC = 'jtl_connector_link_specific'; + public const string SPECIFIC_VALUE = 'jtl_connector_link_specific_value'; + public const string TAX_CLASS = 'jtl_connector_link_tax_class'; +} diff --git a/src/Utilities/SqlTraits/CategoryTrait.php b/src/Utilities/SqlTraits/CategoryTrait.php index 57f324ca..21b31099 100644 --- a/src/Utilities/SqlTraits/CategoryTrait.php +++ b/src/Utilities/SqlTraits/CategoryTrait.php @@ -5,6 +5,7 @@ namespace JtlWooCommerceConnector\Utilities\SqlTraits; use JtlWooCommerceConnector\Utilities\Category as CategoryUtil; +use JtlWooCommerceConnector\Utilities\LinkTableNames; trait CategoryTrait { @@ -129,7 +130,7 @@ public static function categoryTreePreOrder(int $categoryId, int $level): string public static function categoryPull(int $limit): string { global $wpdb; - $jclc = $wpdb->prefix . 'jtl_connector_link_category'; + $jclc = $wpdb->prefix . LinkTableNames::CATEGORY; return \sprintf( " @@ -153,7 +154,7 @@ public static function categoryPull(int $limit): string public static function categoryStats(): string { global $wpdb; - $jclc = $wpdb->prefix . 'jtl_connector_link_category'; + $jclc = $wpdb->prefix . LinkTableNames::CATEGORY; return \sprintf( " diff --git a/src/Utilities/SqlTraits/CrossSellingTrait.php b/src/Utilities/SqlTraits/CrossSellingTrait.php index 4d8e2445..b9416926 100644 --- a/src/Utilities/SqlTraits/CrossSellingTrait.php +++ b/src/Utilities/SqlTraits/CrossSellingTrait.php @@ -4,6 +4,8 @@ namespace JtlWooCommerceConnector\Utilities\SqlTraits; +use JtlWooCommerceConnector\Utilities\LinkTableNames; + /** * Trait CrossSellingTrait * @@ -18,14 +20,14 @@ trait CrossSellingTrait public static function crossSellingPull(?int $limit = null): string { global $wpdb; - $jclc = $wpdb->prefix . 'jtl_connector_link_crossselling'; - $limitQuery = \is_null($limit) ? '' : 'LIMIT ' . $limit; + $jclc = $wpdb->prefix . LinkTableNames::CROSSSELLING; + $limitQuery = \is_null($limit) ? '' : $wpdb->prepare(' LIMIT %d', $limit); $select = 'SELECT pm.post_id, GROUP_CONCAT(pm.meta_value SEPARATOR "||") AS meta_value, GROUP_CONCAT(pm.meta_key SEPARATOR "||") AS meta_key'; if ($limit === null) { $select = 'SELECT COUNT(DISTINCT pm.post_id) as total'; - $limitQuery = 'LIMIT 1'; + $limitQuery = ' LIMIT 1'; } return " diff --git a/src/Utilities/SqlTraits/CustomerGroupTrait.php b/src/Utilities/SqlTraits/CustomerGroupTrait.php index 77e399a7..92098a56 100644 --- a/src/Utilities/SqlTraits/CustomerGroupTrait.php +++ b/src/Utilities/SqlTraits/CustomerGroupTrait.php @@ -4,6 +4,8 @@ namespace JtlWooCommerceConnector\Utilities\SqlTraits; +use JtlWooCommerceConnector\Utilities\LinkTableNames; + trait CustomerGroupTrait { /** @@ -12,7 +14,7 @@ trait CustomerGroupTrait public static function customerGroupPull(): string { global $wpdb; - $jclcg = $wpdb->prefix . 'jtl_connector_link_customer_group'; + $jclcg = $wpdb->prefix . LinkTableNames::CUSTOMER_GROUP; return " SELECT p.ID, p.post_title, p.post_name @@ -30,7 +32,7 @@ public static function customerGroupPull(): string public static function customerGroupPullRole(): string { global $wpdb; - $jclcg = $wpdb->prefix . 'jtl_connector_link_customer_group'; + $jclcg = $wpdb->prefix . LinkTableNames::CUSTOMER_GROUP; return " SELECT p.ID, p.post_title, p.post_name diff --git a/src/Utilities/SqlTraits/CustomerOrderTrait.php b/src/Utilities/SqlTraits/CustomerOrderTrait.php index b51e3ff5..cca1ce12 100644 --- a/src/Utilities/SqlTraits/CustomerOrderTrait.php +++ b/src/Utilities/SqlTraits/CustomerOrderTrait.php @@ -5,6 +5,7 @@ namespace JtlWooCommerceConnector\Utilities\SqlTraits; use JtlWooCommerceConnector\Utilities\Config; +use JtlWooCommerceConnector\Utilities\LinkTableNames; use JtlWooCommerceConnector\Utilities\Util; trait CustomerOrderTrait @@ -16,7 +17,7 @@ trait CustomerOrderTrait public static function customerOrderPull(?int $limit): string { global $wpdb; - $jclo = $wpdb->prefix . 'jtl_connector_link_order'; + $jclo = $wpdb->prefix . LinkTableNames::ORDER; if (\is_null($limit)) { $select = 'COUNT(DISTINCT(p.ID))'; diff --git a/src/Utilities/SqlTraits/CustomerTrait.php b/src/Utilities/SqlTraits/CustomerTrait.php index d97d6ccc..ebb9284d 100644 --- a/src/Utilities/SqlTraits/CustomerTrait.php +++ b/src/Utilities/SqlTraits/CustomerTrait.php @@ -6,6 +6,7 @@ use JtlWooCommerceConnector\Utilities\Config; use JtlWooCommerceConnector\Utilities\Id; +use JtlWooCommerceConnector\Utilities\LinkTableNames; use JtlWooCommerceConnector\Utilities\SupportedPlugins; use JtlWooCommerceConnector\Utilities\Util; use Psr\Log\InvalidArgumentException; @@ -41,7 +42,7 @@ public static function customerNotLinked(?int $limit, LoggerInterface|NullLogger private static function customerNotLinkedNoCondition(?int $limit): string { global $wpdb; - $jclc = $wpdb->prefix . 'jtl_connector_link_customer'; + $jclc = $wpdb->prefix . LinkTableNames::CUSTOMER; if (\is_null($limit)) { $select = 'COUNT(DISTINCT(um.user_id))'; @@ -83,7 +84,7 @@ private static function customerNotLinkedNoCondition(?int $limit): string private static function customerNotLinkedCondition(?int $limit, LoggerInterface|NullLogger $logger): string { global $wpdb; - $jclc = $wpdb->prefix . 'jtl_connector_link_customer'; + $jclc = $wpdb->prefix . LinkTableNames::CUSTOMER; $status = Util::getOrderStatusesToImport(); @@ -127,7 +128,7 @@ private static function customerNotLinkedCondition(?int $limit, LoggerInterface| public static function guestNotLinked(?int $limit, LoggerInterface|NullLogger $logger): string { global $wpdb; - $jclc = $wpdb->prefix . 'jtl_connector_link_customer'; + $jclc = $wpdb->prefix . LinkTableNames::CUSTOMER; $guestPrefix = Id::GUEST_PREFIX . Id::SEPARATOR; @@ -174,7 +175,7 @@ public static function guestNotLinked(?int $limit, LoggerInterface|NullLogger $l private static function getCustomerPullCondition(LoggerInterface|NullLogger $logger): string { global $wpdb; - $jclo = $wpdb->prefix . 'jtl_connector_link_order'; + $jclo = $wpdb->prefix . LinkTableNames::ORDER; $logger->debug('Customer Pull Condition: ' . Config::get(Config::OPTIONS_LIMIT_CUSTOMER_QUERY_TYPE)); diff --git a/src/Utilities/SqlTraits/GermanMarketTrait.php b/src/Utilities/SqlTraits/GermanMarketTrait.php index e3b5e1a1..c638a9ef 100644 --- a/src/Utilities/SqlTraits/GermanMarketTrait.php +++ b/src/Utilities/SqlTraits/GermanMarketTrait.php @@ -32,8 +32,11 @@ public static function globalDataGMMUPullSpecific(): string $wat = $wpdb->prefix . 'woocommerce_attribute_taxonomies'; - return "SELECT wat.attribute_id, wat.attribute_name, wat.attribute_label, wat.attribute_type + return (string) $wpdb->prepare( + "SELECT wat.attribute_id, wat.attribute_name, wat.attribute_label, wat.attribute_type FROM {$wat} wat - WHERE wat.attribute_name = '{$slug}'"; + WHERE wat.attribute_name = %s", + $slug + ); } } diff --git a/src/Utilities/SqlTraits/GermanizedDataTrait.php b/src/Utilities/SqlTraits/GermanizedDataTrait.php index 5bebd641..8ac8556f 100644 --- a/src/Utilities/SqlTraits/GermanizedDataTrait.php +++ b/src/Utilities/SqlTraits/GermanizedDataTrait.php @@ -35,12 +35,15 @@ public static function deliveryStatusByText(string $status): string { global $wpdb; - return " + return (string) $wpdb->prepare( + " SELECT tt.term_id FROM {$wpdb->terms} t LEFT JOIN {$wpdb->term_taxonomy} tt ON tt.term_id = t.term_id WHERE tt.taxonomy = 'product_delivery_time' - AND t.name = '{$status}'"; + AND t.name = %s", + $status + ); } } diff --git a/src/Utilities/SqlTraits/ImageTrait.php b/src/Utilities/SqlTraits/ImageTrait.php index 1340eb62..83015ad1 100644 --- a/src/Utilities/SqlTraits/ImageTrait.php +++ b/src/Utilities/SqlTraits/ImageTrait.php @@ -8,6 +8,7 @@ use JtlWooCommerceConnector\Utilities\Category as CategoryUtil; use JtlWooCommerceConnector\Controllers\ImageController as ImageCtrl; use JtlWooCommerceConnector\Utilities\Id; +use JtlWooCommerceConnector\Utilities\LinkTableNames; trait ImageTrait { @@ -21,7 +22,7 @@ public static function imageCategoryPull(?int $limit = null): string $limitQuery = \is_null($limit) ? '' : 'LIMIT ' . $limit; list($table, $column) = CategoryUtil::getTermMetaData(); - $jcli = $wpdb->prefix . 'jtl_connector_link_image'; + $jcli = $wpdb->prefix . LinkTableNames::IMAGE; return \sprintf( " @@ -58,7 +59,7 @@ public static function imageManufacturerPull(?int $limit = null): string global $wpdb; $limitQuery = \is_null($limit) ? '' : 'LIMIT ' . $limit; - $jcli = $wpdb->prefix . 'jtl_connector_link_image'; + $jcli = $wpdb->prefix . LinkTableNames::IMAGE; $sql = \sprintf( " SELECT CONCAT_WS('%s', '%s', p.ID) as id, p.ID as ID, tt.term_id as parent, p.guid @@ -91,7 +92,7 @@ public static function imageManufacturerPull(?int $limit = null): string public static function imageProductThumbnail(): string { global $wpdb; - $jcli = $wpdb->prefix . 'jtl_connector_link_image'; + $jcli = $wpdb->prefix . LinkTableNames::IMAGE; return \sprintf( " @@ -139,7 +140,7 @@ public static function imageProductGalleryStats(): string public static function linkedProductImages(): string { global $wpdb; - $jcli = $wpdb->prefix . 'jtl_connector_link_image'; + $jcli = $wpdb->prefix . LinkTableNames::IMAGE; return \sprintf( " @@ -157,7 +158,7 @@ public static function linkedProductImages(): string public static function imageVariationCombinationPull(?int $limit = null): string { global $wpdb; - $jcli = $wpdb->prefix . 'jtl_connector_link_image'; + $jcli = $wpdb->prefix . LinkTableNames::IMAGE; $limitQuery = \is_null($limit) ? '' : 'LIMIT ' . $limit; @@ -228,7 +229,7 @@ public static function countRelatedProducts(int $attachementId): string public static function imageDeleteLinks(int $productId): string { global $wpdb; - $jcli = $wpdb->prefix . 'jtl_connector_link_image'; + $jcli = $wpdb->prefix . LinkTableNames::IMAGE; return \sprintf( " @@ -251,18 +252,19 @@ public static function imageDeleteLinks(int $productId): string public static function imageDeleteLink(int $attachmentId, int $productId): string { global $wpdb; - $jcli = $wpdb->prefix . 'jtl_connector_link_image'; + $jcli = $wpdb->prefix . LinkTableNames::IMAGE; return \sprintf( " DELETE FROM {$jcli} WHERE (`type` = %d OR `type` = %d) - AND endpoint_id - LIKE '{$attachmentId}%s{$productId}'", + AND endpoint_id = '%d%s%d'", IdentityType::PRODUCT_IMAGE, IdentityType::PRODUCT, - Id::SEPARATOR + $attachmentId, + Id::SEPARATOR, + $productId ); } } diff --git a/src/Utilities/SqlTraits/ManufacturerTrait.php b/src/Utilities/SqlTraits/ManufacturerTrait.php index e11ec279..271f3821 100644 --- a/src/Utilities/SqlTraits/ManufacturerTrait.php +++ b/src/Utilities/SqlTraits/ManufacturerTrait.php @@ -11,6 +11,8 @@ namespace JtlWooCommerceConnector\Utilities\SqlTraits; +use JtlWooCommerceConnector\Utilities\LinkTableNames; + trait ManufacturerTrait { /* @@ -18,7 +20,7 @@ trait ManufacturerTrait { global $wpdb; $wat = $wpdb->prefix . 'woocommerce_attribute_taxonomies'; - $jcls = $wpdb->prefix . 'jtl_connector_link_specific'; + $jcls = $wpdb->prefix . LinkTableNames::SPECIFIC; return "SELECT wat.attribute_id, wat.attribute_name, wat.attribute_label, wat.attribute_type FROM {$wat} wat @@ -30,7 +32,7 @@ trait ManufacturerTrait public static function specificValuePull($specificName) { global $wpdb; - $jclsv = $wpdb->prefix . 'jtl_connector_link_specific_value'; + $jclsv = $wpdb->prefix . LinkTableNames::SPECIFIC_VALUE; return "SELECT t.term_id, t.name, tt.term_taxonomy_id, tt.taxonomy, t.slug FROM {$wpdb->terms} t @@ -46,7 +48,7 @@ public static function specificValuePull($specificName) public static function forceSpecificValuePull($specificName) { global $wpdb; - $jclsv = $wpdb->prefix . 'jtl_connector_link_specific_value'; + $jclsv = $wpdb->prefix . LinkTableNames::SPECIFIC_VALUE; return "SELECT t.term_id, t.name, tt.term_taxonomy_id, tt.taxonomy, t.slug FROM {$wpdb->terms} t @@ -64,7 +66,7 @@ public static function forceSpecificValuePull($specificName) public static function manufacturerStats(string $taxonomy): string { global $wpdb; - $jclm = $wpdb->prefix . 'jtl_connector_link_manufacturer'; + $jclm = $wpdb->prefix . LinkTableNames::MANUFACTURER; return \sprintf( " @@ -85,7 +87,7 @@ public static function manufacturerStats(string $taxonomy): string public static function manufacturerPull(int $limit, string $taxonomy): string { global $wpdb; - $jclm = $wpdb->prefix . 'jtl_connector_link_manufacturer'; + $jclm = $wpdb->prefix . LinkTableNames::MANUFACTURER; return \sprintf( " @@ -127,7 +129,7 @@ public static function pullRankMathSeoTermData(int $termId): string Public static function getSpecificValueId($specificName, $specificValueName) { global $wpdb; - $jclsv = $wpdb->prefix . 'jtl_connector_link_specific_value'; + $jclsv = $wpdb->prefix . LinkTableNames::SPECIFIC_VALUE; return "SELECT lsv.host_id , lsv.endpoint_id, t.term_id, t.name, tt.term_taxonomy_id, tt.taxonomy, t.slug FROM {$wpdb->terms} t @@ -142,7 +144,7 @@ public static function getSpecificId($specificName) global $wpdb; $wat = $wpdb->prefix . 'woocommerce_attribute_taxonomies'; - $jcls = $wpdb->prefix . 'jtl_connector_link_specific'; + $jcls = $wpdb->prefix . LinkTableNames::SPECIFIC; return "SELECT wat.attribute_id FROM {$wat} wat @@ -155,7 +157,7 @@ public static function removeSpecificLinking($id) { global $wpdb; - $jcls = $wpdb->prefix . 'jtl_connector_link_specific'; + $jcls = $wpdb->prefix . LinkTableNames::SPECIFIC; return "DELETE FROM {$jcls} WHERE endpoint_id = '{$id}';"; } @@ -164,7 +166,7 @@ public static function removeSpecificValueLinking($id) { global $wpdb; - $jcls = $wpdb->prefix . 'jtl_connector_link_specific_value'; + $jcls = $wpdb->prefix . LinkTableNames::SPECIFIC_VALUE; return "DELETE FROM {$jcls} WHERE endpoint_id = '{$id}';"; } diff --git a/src/Utilities/SqlTraits/PaymentTrait.php b/src/Utilities/SqlTraits/PaymentTrait.php index 56a8171d..4005ec9f 100644 --- a/src/Utilities/SqlTraits/PaymentTrait.php +++ b/src/Utilities/SqlTraits/PaymentTrait.php @@ -5,6 +5,7 @@ namespace JtlWooCommerceConnector\Utilities\SqlTraits; use JtlWooCommerceConnector\Utilities\Config; +use JtlWooCommerceConnector\Utilities\LinkTableNames; use JtlWooCommerceConnector\Utilities\Util; use function Symfony\Component\String\s; @@ -20,8 +21,8 @@ trait PaymentTrait public static function paymentCompletedPull(bool $includeCompletedOrders, ?int $limit = null): string { global $wpdb; - $jclp = $wpdb->prefix . 'jtl_connector_link_payment'; - $jclo = $wpdb->prefix . 'jtl_connector_link_order'; + $jclp = $wpdb->prefix . LinkTableNames::PAYMENT; + $jclo = $wpdb->prefix . LinkTableNames::ORDER; /** @var string $since */ $since = Config::get(Config::OPTIONS_PULL_ORDERS_SINCE); diff --git a/src/Utilities/SqlTraits/PrimaryKeyMappingTrait.php b/src/Utilities/SqlTraits/PrimaryKeyMappingTrait.php index 5c6770b7..9b952ddd 100644 --- a/src/Utilities/SqlTraits/PrimaryKeyMappingTrait.php +++ b/src/Utilities/SqlTraits/PrimaryKeyMappingTrait.php @@ -4,6 +4,8 @@ namespace JtlWooCommerceConnector\Utilities\SqlTraits; +use JtlWooCommerceConnector\Utilities\LinkTableNames; + trait PrimaryKeyMappingTrait { /** @@ -14,7 +16,7 @@ trait PrimaryKeyMappingTrait public static function primaryKeyMappingHostImage(string $endpointId, int $type): string { global $wpdb; - $jcli = $wpdb->prefix . 'jtl_connector_link_image'; + $jcli = $wpdb->prefix . LinkTableNames::IMAGE; return "SELECT host_id FROM {$jcli} @@ -29,7 +31,7 @@ public static function primaryKeyMappingHostImage(string $endpointId, int $type) public static function primaryKeyMappingHostCustomer(string $endpointId, int $isGuest): string { global $wpdb; - $jclc = $wpdb->prefix . 'jtl_connector_link_customer'; + $jclc = $wpdb->prefix . LinkTableNames::CUSTOMER; return "SELECT `host_id` FROM {$jclc} @@ -91,7 +93,7 @@ public static function primaryKeyMappingEndpoint(int $hostId, string $tableName, public static function primaryKeyMappingSaveImage(string $endpointId, int $hostId, int $type): string { global $wpdb; - $jcli = $wpdb->prefix . 'jtl_connector_link_image'; + $jcli = $wpdb->prefix . LinkTableNames::IMAGE; return "INSERT INTO {$jcli} (endpoint_id, host_id, `type`) VALUES ('{$wpdb->_escape($endpointId)}', {$hostId}, {$type})"; @@ -106,7 +108,7 @@ public static function primaryKeyMappingSaveImage(string $endpointId, int $hostI public static function primaryKeyMappingSaveCustomer(string $endpointId, int $hostId, int $isGuest): string { global $wpdb; - $jclc = $wpdb->prefix . 'jtl_connector_link_customer'; + $jclc = $wpdb->prefix . LinkTableNames::CUSTOMER; return "INSERT INTO {$jclc} (endpoint_id, host_id, is_guest) VALUES ('{$wpdb->_escape($endpointId)}', {$hostId}, {$isGuest})"; @@ -162,23 +164,23 @@ public static function primaryKeyMappingClear(): array { global $wpdb; $tables = [ - "jtl_connector_link_category", - "jtl_connector_link_crossselling", - "jtl_connector_link_crossselling_group", - "jtl_connector_link_currency", - "jtl_connector_link_customer", - "jtl_connector_link_customer_group", - "jtl_connector_link_image", - "jtl_connector_link_language", - "jtl_connector_link_manufacturer", - "jtl_connector_link_manufacturer_unit", - "jtl_connector_link_order", - "jtl_connector_link_payment", - "jtl_connector_link_product", - "jtl_connector_link_shipping_class", - "jtl_connector_link_shipping_method", - "jtl_connector_link_specific", - "jtl_connector_link_specific_value", + LinkTableNames::CATEGORY, + LinkTableNames::CROSSSELLING, + LinkTableNames::CROSSSELLING_GROUP, + LinkTableNames::CURRENCY, + LinkTableNames::CUSTOMER, + LinkTableNames::CUSTOMER_GROUP, + LinkTableNames::IMAGE, + LinkTableNames::LANGUAGE, + LinkTableNames::MANUFACTURER, + LinkTableNames::MANUFACTURER_UNIT, + LinkTableNames::ORDER, + LinkTableNames::PAYMENT, + LinkTableNames::PRODUCT, + LinkTableNames::SHIPPING_CLASS, + LinkTableNames::SHIPPING_METHOD, + LinkTableNames::SPECIFIC, + LinkTableNames::SPECIFIC_VALUE, ]; $arr = []; diff --git a/src/Utilities/SqlTraits/ProductTrait.php b/src/Utilities/SqlTraits/ProductTrait.php index 518a1f5b..d20cf78d 100644 --- a/src/Utilities/SqlTraits/ProductTrait.php +++ b/src/Utilities/SqlTraits/ProductTrait.php @@ -11,6 +11,8 @@ namespace JtlWooCommerceConnector\Utilities\SqlTraits; +use JtlWooCommerceConnector\Utilities\LinkTableNames; + trait ProductTrait { /** @@ -20,7 +22,7 @@ trait ProductTrait public static function productPull(?int $limit = null): string { global $wpdb; - $jclp = $wpdb->prefix . 'jtl_connector_link_product'; + $jclp = $wpdb->prefix . LinkTableNames::PRODUCT; $limitQuery = \is_null($limit) ? '' : 'LIMIT ' . $limit; diff --git a/src/Utilities/SqlTraits/SpecificTrait.php b/src/Utilities/SqlTraits/SpecificTrait.php index 7b75da79..bc00e820 100644 --- a/src/Utilities/SqlTraits/SpecificTrait.php +++ b/src/Utilities/SqlTraits/SpecificTrait.php @@ -4,6 +4,8 @@ namespace JtlWooCommerceConnector\Utilities\SqlTraits; +use JtlWooCommerceConnector\Utilities\LinkTableNames; + trait SpecificTrait { /** @@ -14,7 +16,7 @@ public static function specificPull(int $limit): string { global $wpdb; $wat = $wpdb->prefix . 'woocommerce_attribute_taxonomies'; - $jcls = $wpdb->prefix . 'jtl_connector_link_specific'; + $jcls = $wpdb->prefix . LinkTableNames::SPECIFIC; return "SELECT wat.attribute_id, wat.attribute_name, wat.attribute_label, wat.attribute_type FROM {$wat} wat @@ -30,7 +32,7 @@ public static function specificPull(int $limit): string public static function specificValuePull(string $specificName): string { global $wpdb; - $jclsv = $wpdb->prefix . 'jtl_connector_link_specific_value'; + $jclsv = $wpdb->prefix . LinkTableNames::SPECIFIC_VALUE; return "SELECT t.term_id, t.name, tt.term_taxonomy_id, tt.taxonomy, t.slug, tt.description FROM {$wpdb->terms} t @@ -50,7 +52,7 @@ public static function specificValuePull(string $specificName): string public static function forceSpecificValuePull(string $specificName): string { global $wpdb; - $jclsv = $wpdb->prefix . 'jtl_connector_link_specific_value'; + $jclsv = $wpdb->prefix . LinkTableNames::SPECIFIC_VALUE; return "SELECT t.term_id, t.name, tt.term_taxonomy_id, tt.taxonomy, t.slug FROM {$wpdb->terms} t @@ -67,7 +69,7 @@ public static function specificStats(): string { global $wpdb; $wat = $wpdb->prefix . 'woocommerce_attribute_taxonomies'; - $jcls = $wpdb->prefix . 'jtl_connector_link_specific'; + $jcls = $wpdb->prefix . LinkTableNames::SPECIFIC; return \sprintf(" SELECT COUNT(at.attribute_id) @@ -84,7 +86,7 @@ public static function specificStats(): string public static function getSpecificValueId(string $specificName, string $specificValueName): string { global $wpdb; - $jclsv = $wpdb->prefix . 'jtl_connector_link_specific_value'; + $jclsv = $wpdb->prefix . LinkTableNames::SPECIFIC_VALUE; return "SELECT lsv.host_id , lsv.endpoint_id, t.term_id, t.name, tt.term_taxonomy_id, tt.taxonomy, t.slug FROM {$wpdb->terms} t @@ -103,7 +105,7 @@ public static function getSpecificValueId(string $specificName, string $specific public static function getSpecificValueIdBySlug(string $specificName, string $specificValueName): string { global $wpdb; - $jclsv = $wpdb->prefix . 'jtl_connector_link_specific_value'; + $jclsv = $wpdb->prefix . LinkTableNames::SPECIFIC_VALUE; return "SELECT lsv.host_id , lsv.endpoint_id, t.term_id, t.name, tt.term_taxonomy_id, tt.taxonomy, t.slug FROM {$wpdb->terms} t @@ -155,7 +157,7 @@ public static function getSpecificId(string $specificName): string global $wpdb; $wat = $wpdb->prefix . 'woocommerce_attribute_taxonomies'; - $jcls = $wpdb->prefix . 'jtl_connector_link_specific'; + $jcls = $wpdb->prefix . LinkTableNames::SPECIFIC; return "SELECT wat.attribute_id FROM {$wat} wat @@ -172,7 +174,7 @@ public static function removeSpecificLinking(int $id): string { global $wpdb; - $jcls = $wpdb->prefix . 'jtl_connector_link_specific'; + $jcls = $wpdb->prefix . LinkTableNames::SPECIFIC; return "DELETE FROM {$jcls} WHERE endpoint_id = '{$id}';"; } @@ -185,7 +187,7 @@ public static function removeSpecificValueLinking(int $id): string { global $wpdb; - $jcls = $wpdb->prefix . 'jtl_connector_link_specific_value'; + $jcls = $wpdb->prefix . LinkTableNames::SPECIFIC_VALUE; return "DELETE FROM {$jcls} WHERE endpoint_id = '{$id}';"; } diff --git a/src/Utilities/SupportedPlugins.php b/src/Utilities/SupportedPlugins.php index 2fc38e3f..03f247b2 100755 --- a/src/Utilities/SupportedPlugins.php +++ b/src/Utilities/SupportedPlugins.php @@ -8,7 +8,7 @@ class SupportedPlugins { //THEMESPECIALS - public const + public const string //Compatible PLUGIN_B2B_MARKET = 'B2B Market', PLUGIN_GERMAN_MARKET = 'German Market', @@ -56,7 +56,7 @@ class SupportedPlugins PLUGIN_BACKWPUP = 'BackWPup'; //arrays - public const SUPPORTED_PLUGINS = [ + public const array SUPPORTED_PLUGINS = [ self::PLUGIN_PERFECT_WOO_BRANDS, self::PLUGIN_PERFECT_BRANDS_FOR_WOOCOMMERCE, self::PLUGIN_PERFECT_BRANDS_WOOCOMMERCE, @@ -91,7 +91,7 @@ class SupportedPlugins self::PLUGIN_ADVANCED_CUSTOM_FIELDS, ]; - public const INCOMPATIBLE_PLUGINS = [ + public const array INCOMPATIBLE_PLUGINS = [ self::PLUGIN_ANTISPAM_BEE, self::PLUGIN_CERBER_SECURITY, self::PLUGIN_WORDFENCE, diff --git a/src/Utilities/Util.php b/src/Utilities/Util.php index d17352bf..85f3e309 100644 --- a/src/Utilities/Util.php +++ b/src/Utilities/Util.php @@ -21,9 +21,9 @@ */ class Util extends WordpressUtils { - public const TO_SYNC = 'jtlconnector_master_products_to_sync'; - public const TO_SYNC_COUNT = 'jtlconnector_master_products_to_sync_count'; - public const TO_SYNC_MOD = 100; + public const string TO_SYNC = 'jtlconnector_master_products_to_sync'; + public const string TO_SYNC_COUNT = 'jtlconnector_master_products_to_sync_count'; + public const int TO_SYNC_MOD = 100; private string $locale; @@ -69,7 +69,8 @@ public function getTaxRateByTaxClass(string $taxClass, ?\WC_Order $order = null) if (!\is_string($wcDefaultCountry)) { throw new \InvalidArgumentException( - "Expected wcDefaultCountry to be a string but got " . \gettype($wcDefaultCountry) . " instead." + "Expected wcDefaultCountry to be a string but got " + . \esc_html(\gettype($wcDefaultCountry)) . " instead." ); } @@ -263,7 +264,7 @@ public function addMasterProductToSync(int $productId): void if (!\is_int($masterProductsToSyncCount)) { throw new \InvalidArgumentException( "Expected masterProductsToSyncCount to be an integer but got " . - \gettype($masterProductsToSyncCount) . " instead." + \esc_html(\gettype($masterProductsToSyncCount)) . " instead." ); } @@ -273,7 +274,7 @@ public function addMasterProductToSync(int $productId): void if (!\is_array($masterProductsToSync)) { throw new \InvalidArgumentException( "Expected masterProductsToSync to be an array but got " . - \gettype($masterProductsToSync) . " instead." + \esc_html(\gettype($masterProductsToSync)) . " instead." ); } @@ -299,8 +300,10 @@ public function syncMasterProducts(): void if (!\is_array($masterProductsToSync)) { throw new \InvalidArgumentException( - "Expected masterProductsToSync to be an array but got " . - \gettype($masterProductsToSync) . " instead." + \esc_html( + "Expected masterProductsToSync to be an array but got " . + \gettype($masterProductsToSync) . " instead." + ) ); } @@ -534,7 +537,7 @@ public static function getOrderStatusesToImport(): array if (!\is_array($orderImportStatuses)) { throw new \InvalidArgumentException( "Expected orderImportStatuses to be an array but got " - . \gettype($orderImportStatuses) . " instead." + . \esc_html(\gettype($orderImportStatuses)) . " instead." ); } @@ -554,7 +557,7 @@ public static function getManualPaymentTypes(): array if (!\is_array($manualPaymentTypes)) { throw new \InvalidArgumentException( "Expected manualPaymentTypes to be an array but got " - . \gettype($manualPaymentTypes) . " instead." + . \esc_html(\gettype($manualPaymentTypes)) . " instead." ); } @@ -723,7 +726,7 @@ public function findAttributeValue( if (!\is_string($value)) { throw new \InvalidArgumentException( - "Expected value to be a string but got " . \gettype($value) . " instead." + "Expected value to be a string but got " . \esc_html(\gettype($value)) . " instead." ); } diff --git a/tests/src/ClassInitializationTest.php b/tests/src/ClassInitializationTest.php index 8cd018df..e512477d 100644 --- a/tests/src/ClassInitializationTest.php +++ b/tests/src/ClassInitializationTest.php @@ -59,6 +59,13 @@ public function testClassInitialization(): void $classNameWithNamespace = \sprintf('JtlWooCommerceConnector%s\%s', $namespace, $className); + if ( + \class_exists($classNameWithNamespace) + && (new \ReflectionClass($classNameWithNamespace))->isFinal() + ) { + continue; + } + $mock = $this->getMockBuilder($classNameWithNamespace)->disableOriginalConstructor()->getMock(); $this->assertInstanceOf($classNameWithNamespace, $mock); @@ -71,6 +78,6 @@ public function testClassInitialization(): void */ protected function findClasses(string $srcPath): bool|array { - return \glob(\sprintf("%s/{,*/,*/*/,*/*/*/}*.php", $srcPath), 1024); + return \glob(\sprintf("%s/{,*/,*/*/,*/*/*/}*.php", $srcPath), \GLOB_BRACE); } } diff --git a/tests/src/Regression/CO2770/ImageTest.php b/tests/src/Regression/CO2770/ImageTest.php index 2f27ae7a..a18f20b5 100644 --- a/tests/src/Regression/CO2770/ImageTest.php +++ b/tests/src/Regression/CO2770/ImageTest.php @@ -12,6 +12,7 @@ use Jtl\Connector\Core\Model\ProductImage; use JtlWooCommerceConnector\Controllers\ImageController; use JtlWooCommerceConnector\Utilities\Db; +use JtlWooCommerceConnector\Utilities\LinkTableNames; use JtlWooCommerceConnector\Utilities\Util; use phpmock\MockBuilder; use phpmock\MockEnabledException; @@ -108,7 +109,7 @@ public function deleteProductImageDataProvider(): array (new ProductImage())->setName('Default name')->setId(new Identity("1111_2222", 1)), true, " - DELETE FROM wp_jtl_connector_link_image + DELETE FROM wp_" . LinkTableNames::IMAGE . " WHERE (`type` = 42 OR `type` = 64) AND endpoint_id diff --git a/tests/src/Security/AuthorizationTest.php b/tests/src/Security/AuthorizationTest.php new file mode 100644 index 00000000..85a98f35 --- /dev/null +++ b/tests/src/Security/AuthorizationTest.php @@ -0,0 +1,216 @@ +extractMethodSource(self::ADMIN_FILE, 'public static function save'); + + $this->assertStringContainsString( + "current_user_can('manage_woocommerce')", + $source, + 'JtlConnectorAdmin::save() must check for manage_woocommerce capability' + ); + } + + /** + * @return void + */ + public function testSaveMethodContainsNonceVerification(): void + { + $source = $this->extractMethodSource(self::ADMIN_FILE, 'public static function save'); + + $this->assertStringContainsString( + "check_admin_referer('settings_save_woo-jtl-connector')", + $source, + 'JtlConnectorAdmin::save() must verify the admin referer nonce' + ); + } + + /** + * @return void + */ + public function testSettingsFormContainsNonceField(): void + { + $source = (string)\file_get_contents(self::ADMIN_FILE); + + $this->assertStringContainsString( + "wp_nonce_field('settings_save_woo-jtl-connector')", + $source, + 'The settings form must include a wp_nonce_field for CSRF protection' + ); + } + + /** + * @return void + */ + public function testDownloadJTLLogsContainsCapabilityCheck(): void + { + $source = $this->extractFunctionSource(self::PLUGIN_FILE, 'function jtlwcc_download_logs'); + + $this->assertStringContainsString( + "current_user_can('manage_woocommerce')", + $source, + 'jtlwcc_download_logs() must check for manage_woocommerce capability' + ); + } + + /** + * @return void + */ + public function testDownloadJTLLogsContainsNonceVerification(): void + { + $source = $this->extractFunctionSource(self::PLUGIN_FILE, 'function jtlwcc_download_logs'); + + $this->assertStringContainsString( + "check_ajax_referer('jtl_logs_nonce')", + $source, + 'jtlwcc_download_logs() must verify the AJAX nonce' + ); + } + + /** + * @return void + */ + public function testClearJTLLogsContainsCapabilityCheck(): void + { + $source = $this->extractFunctionSource(self::PLUGIN_FILE, 'function jtlwcc_clear_logs'); + + $this->assertStringContainsString( + "current_user_can('manage_woocommerce')", + $source, + 'jtlwcc_clear_logs() must check for manage_woocommerce capability' + ); + } + + /** + * @return void + */ + public function testClearJTLLogsContainsNonceVerification(): void + { + $source = $this->extractFunctionSource(self::PLUGIN_FILE, 'function jtlwcc_clear_logs'); + + $this->assertStringContainsString( + "check_ajax_referer('jtl_logs_nonce')", + $source, + 'jtlwcc_clear_logs() must verify the AJAX nonce' + ); + } + + /** + * @return void + */ + public function testDownloadJTLLogsDoesNotWriteToPublicPath(): void + { + $source = $this->extractFunctionSource(self::PLUGIN_FILE, 'function jtlwcc_download_logs'); + + $this->assertStringNotContainsString( + 'wp-content/plugins', + $source, + 'jtlwcc_download_logs() must not write ZIP files to a publicly accessible path' + ); + } + + /** + * @return void + */ + public function testJavascriptSendsNonceWithAjaxCalls(): void + { + $source = $this->extractFunctionSource( + self::PLUGIN_FILE, + 'function jtlwcc_settings_javascript' + ); + + $this->assertStringContainsString( + '_ajax_nonce', + $source, + 'AJAX calls must include a nonce parameter' + ); + + $this->assertStringContainsString( + "wp_create_nonce('jtl_logs_nonce')", + $source, + 'The JavaScript must use a nonce created with wp_create_nonce' + ); + } + + /** + * Extracts a function body from a file by searching for the function signature. + * + * @param string $filePath + * @param string $signature + * @return string + */ + private function extractFunctionSource(string $filePath, string $signature): string + { + return $this->extractCodeBlock($filePath, $signature); + } + + /** + * Extracts a method body from a file by searching for the method signature. + * + * @param string $filePath + * @param string $signature + * @return string + */ + private function extractMethodSource(string $filePath, string $signature): string + { + return $this->extractCodeBlock($filePath, $signature); + } + + /** + * Extracts a code block (function/method) from source by matching braces. + * + * @param string $filePath + * @param string $signature + * @return string + */ + private function extractCodeBlock(string $filePath, string $signature): string + { + $content = (string)\file_get_contents($filePath); + $pos = \strpos($content, $signature); + + $this->assertNotFalse($pos, "Signature '{$signature}' not found in {$filePath}"); + + $braceStart = \strpos($content, '{', $pos); + $this->assertNotFalse($braceStart, "Opening brace not found after '{$signature}'"); + + $depth = 0; + $length = \strlen($content); + + for ($i = $braceStart; $i < $length; $i++) { + if ($content[$i] === '{') { + $depth++; + } elseif ($content[$i] === '}') { + $depth--; + if ($depth === 0) { + return \substr($content, $pos, $i - $pos + 1); + } + } + } + + $this->fail("Could not find closing brace for '{$signature}'"); + } +} diff --git a/tests/src/Utilities/SqlTraits/ImageTraitTest.php b/tests/src/Utilities/SqlTraits/ImageTraitTest.php index d03ae01d..78f2e888 100644 --- a/tests/src/Utilities/SqlTraits/ImageTraitTest.php +++ b/tests/src/Utilities/SqlTraits/ImageTraitTest.php @@ -4,6 +4,7 @@ namespace src\Utilities\SqlTraits; +use JtlWooCommerceConnector\Utilities\LinkTableNames; use JtlWooCommerceConnector\Utilities\SqlHelper; use Mockery; use PHPUnit\Framework\TestCase; @@ -70,7 +71,7 @@ public function getImageDeleteLinksDataProvider(): array return [ [ 1111, - " DELETE FROM wp_jtl_connector_link_image" . + ' DELETE FROM wp_' . LinkTableNames::IMAGE . " WHERE (`type` = 42" . " OR `type` = 64)" . " AND endpoint_id" . diff --git a/uninstall.php b/uninstall.php index 1ed24d70..200d0ae3 100644 --- a/uninstall.php +++ b/uninstall.php @@ -6,16 +6,20 @@ exit; } +use JtlWooCommerceConnector\Utilities\LinkTableNames; + +require_once __DIR__ . '/vendor/autoload.php'; + global $wpdb; $tables = [ - 'jtl_connector_link_category', - 'jtl_connector_link_crossselling', - 'jtl_connector_link_customer', - 'jtl_connector_link_image', - 'jtl_connector_link_order', - 'jtl_connector_link_payment', - 'jtl_connector_link_product', + LinkTableNames::CATEGORY, + LinkTableNames::CROSSSELLING, + LinkTableNames::CUSTOMER, + LinkTableNames::IMAGE, + LinkTableNames::ORDER, + LinkTableNames::PAYMENT, + LinkTableNames::PRODUCT, 'jtl_connector_category_level', 'jtl_connector_product_checksum', 'jtl_connector_specific', @@ -23,8 +27,14 @@ ]; foreach ($tables as $table) { - $wpdb->query(sprintf('DROP TABLE IF EXISTS %s%s', $wpdb->prefix, $table)); + $table_name = esc_sql($wpdb->prefix . $table); + // phpcs:ignore WordPress.DB -- Table names cannot be used with $wpdb->prepare() placeholders + $wpdb->query("DROP TABLE IF EXISTS `{$table_name}`"); } - -$wpdb->query("DELETE FROM `{$wpdb->options}` WHERE `option_name` LIKE 'jtlconnector_%'"); +$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery + $wpdb->prepare( + "DELETE FROM `{$wpdb->options}` WHERE `option_name` LIKE %s", + 'jtlconnector_%' + ) +); diff --git a/woo-jtl-connector.php b/woo-jtl-connector.php index 70d5af50..6c642093 100644 --- a/woo-jtl-connector.php +++ b/woo-jtl-connector.php @@ -3,9 +3,11 @@ /** * Plugin Name: JTL-Connector for WooCommerce * Description: Connect your woocommerce-shop with JTL-Wawi, the free multichannel-erp for mail order business. - * Version: 2.4.1 - * Requires PHP: 8.0 - * WC tested up to: 8.2 + * Version: 2.4.2 + * Requires PHP: 8.3 + * Requires at least: 6.4 + * Tested up to: 7.0 + * WC tested up to: 10.8.1 * Author: JTL-Software GmbH * Author URI: http://www.jtl-software.de * License: GPL3 @@ -16,6 +18,10 @@ * @author JTL-Software-GmbH */ +if (!defined('ABSPATH')) { + exit; +} + define('JTLWCC_TEXT_DOMAIN', 'woo-jtl-connector'); define('JTLWCC_WOOCOMMERCE_PLUGIN_FILE', 'woocommerce/woocommerce.php'); define('JTLWCC_DS', DIRECTORY_SEPARATOR); @@ -26,15 +32,11 @@ define('CONNECTOR_DIR', __DIR__); // NEED CONNECTOR CORE CHANGES define('JTLWCC_INCLUDES_DIR', plugin_dir_path(__FILE__) . 'includes' . JTLWCC_DS); -if (!defined('ABSPATH')) { - exit; -} - require_once ABSPATH . '/wp-admin/includes/plugin.php'; try { if (file_exists(JTLWCC_CONNECTOR_DIR . '/connector.phar')) { - if (is_writable(sys_get_temp_dir())) { + if (wp_is_writable(sys_get_temp_dir())) { $loader = require('phar://' . JTLWCC_CONNECTOR_DIR . '/connector.phar/vendor/autoload.php'); $loader->add('', JTLWCC_CONNECTOR_DIR . '/plugins'); if (is_dir(JTLWCC_EXT_CONNECTOR_PLUGIN_DIR)) { @@ -88,25 +90,26 @@ 'JtlConnectorAdmin', 'settings_link', ]); - add_action('admin_footer', 'woo_jtl_connector_settings_javascript', PHP_INT_MAX); - add_action('wp_ajax_downloadJTLLogs', 'downloadJTLLogs', PHP_INT_MAX); - add_action('wp_ajax_clearJTLLogs', 'clearJTLLogs', PHP_INT_MAX); + add_action('admin_footer', 'jtlwcc_settings_javascript', PHP_INT_MAX); + add_action('wp_ajax_jtlwcc_download_logs', 'jtlwcc_download_logs', PHP_INT_MAX); + add_action('wp_ajax_jtlwcc_clear_logs', 'jtlwcc_clear_logs', PHP_INT_MAX); } } /** * @return void */ -function woo_jtl_connector_settings_javascript(): void +function jtlwcc_settings_javascript(): void { + $nonce = wp_create_nonce('jtl_logs_nonce'); ?>