summaryrefslogtreecommitdiff
path: root/plugins/Customizer
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/Customizer')
-rw-r--r--plugins/Customizer/Assets/css/README.md39
-rw-r--r--plugins/Customizer/Assets/css/customizer.css316
-rw-r--r--plugins/Customizer/Assets/css/theme.css1
-rw-r--r--plugins/Customizer/Assets/css/themes/Blueboard.css265
-rw-r--r--plugins/Customizer/Assets/css/themes/Breathe.css327
-rw-r--r--plugins/Customizer/Assets/css/themes/Clemson.css341
-rw-r--r--plugins/Customizer/Assets/css/themes/Galaxy.css2713
-rw-r--r--plugins/Customizer/Assets/css/themes/Github.css3260
-rw-r--r--plugins/Customizer/Assets/css/themes/KindaDark.css685
-rw-r--r--plugins/Customizer/Assets/css/themes/Material.css1427
-rw-r--r--plugins/Customizer/Assets/css/userthemes/niebieski.css1
-rw-r--r--plugins/Customizer/Assets/img/logo-gen.pngbin0 -> 7850 bytes
-rw-r--r--plugins/Customizer/Assets/js/customizer.js248
-rw-r--r--plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.css167
-rw-r--r--plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.js492
-rw-r--r--plugins/Customizer/Controller/CustomizerConfigController.php388
-rw-r--r--plugins/Customizer/Controller/CustomizerFileController.php313
-rw-r--r--plugins/Customizer/Helper/DynamicAvatar.php59
-rw-r--r--plugins/Customizer/Helper/ThemeHelper.php64
-rw-r--r--plugins/Customizer/LICENSE21
-rw-r--r--plugins/Customizer/Locale/it_IT/translations.php49
-rw-r--r--plugins/Customizer/Model/CustomizerFileModel.php320
-rw-r--r--plugins/Customizer/Plugin.php173
-rw-r--r--plugins/Customizer/README.md142
-rw-r--r--plugins/Customizer/Schema/Mysql.php23
-rw-r--r--plugins/Customizer/Schema/Postgres.php25
-rw-r--r--plugins/Customizer/Schema/Sqlite.php25
-rw-r--r--plugins/Customizer/Template/board/task_avatar.php26
-rw-r--r--plugins/Customizer/Template/config/sidebar.php3
-rw-r--r--plugins/Customizer/Template/config/themecreator.php96
-rw-r--r--plugins/Customizer/Template/file/remove.php15
-rw-r--r--plugins/Customizer/Template/file/show.php435
-rw-r--r--plugins/Customizer/Template/file/upload_flavicon.php22
-rw-r--r--plugins/Customizer/Template/file/upload_loginlogo.php22
-rw-r--r--plugins/Customizer/Template/file/upload_logo.php22
-rw-r--r--plugins/Customizer/Template/header/title.php24
-rw-r--r--plugins/Customizer/Template/header/title_older_kb.php26
-rw-r--r--plugins/Customizer/Template/header/user_dropdown.php45
-rw-r--r--plugins/Customizer/Template/layout/index.php47
-rw-r--r--plugins/Customizer/Template/layout/layout.php85
-rw-r--r--plugins/Customizer/Template/layout/login_no_custom.php21
-rw-r--r--plugins/Customizer/Template/layout/login_with_custom.php128
-rw-r--r--plugins/Customizer/Template/layout/note.php7
-rw-r--r--plugins/Customizer/Template/layout/preview_style.php77
-rw-r--r--plugins/Customizer/Template/user_mod/show.php58
-rw-r--r--plugins/Customizer/_config.yml3
-rw-r--r--plugins/Customizer/composer.json5
-rw-r--r--plugins/Customizer/composer.lock164
-rw-r--r--plugins/Customizer/vendor/autoload.php7
-rw-r--r--plugins/Customizer/vendor/bin/minifycss1
-rw-r--r--plugins/Customizer/vendor/bin/minifyjs1
-rw-r--r--plugins/Customizer/vendor/composer/ClassLoader.php445
-rw-r--r--plugins/Customizer/vendor/composer/LICENSE21
-rw-r--r--plugins/Customizer/vendor/composer/autoload_classmap.php9
-rw-r--r--plugins/Customizer/vendor/composer/autoload_namespaces.php9
-rw-r--r--plugins/Customizer/vendor/composer/autoload_psr4.php12
-rw-r--r--plugins/Customizer/vendor/composer/autoload_real.php52
-rw-r--r--plugins/Customizer/vendor/composer/autoload_static.php44
-rw-r--r--plugins/Customizer/vendor/composer/installed.json154
-rw-r--r--plugins/Customizer/vendor/luizbills/css-generator/.gitignore2
-rw-r--r--plugins/Customizer/vendor/luizbills/css-generator/LICENSE21
-rw-r--r--plugins/Customizer/vendor/luizbills/css-generator/README.md102
-rw-r--r--plugins/Customizer/vendor/luizbills/css-generator/composer.json21
-rw-r--r--plugins/Customizer/vendor/luizbills/css-generator/demo/demo.php41
-rw-r--r--plugins/Customizer/vendor/luizbills/css-generator/src/Generator.php102
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/CONTRIBUTING.md59
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/Dockerfile13
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/LICENSE18
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/bin/minifycss45
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/bin/minifyjs45
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/composer.json38
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_after.txt7
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_before.txt26
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_reserved.txt63
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators.txt46
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators_after.txt43
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators_before.txt43
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/docker-compose.yml31
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/src/CSS.php736
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/src/Exception.php20
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/BasicException.php23
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php21
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/IOException.php21
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/src/JS.php598
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/minify/src/Minify.php459
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/path-converter/LICENSE18
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/path-converter/composer.json28
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/path-converter/src/Converter.php196
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/path-converter/src/ConverterInterface.php24
-rw-r--r--plugins/Customizer/vendor/matthiasmullie/path-converter/src/NoConverter.php23
90 files changed, 16903 insertions, 0 deletions
diff --git a/plugins/Customizer/Assets/css/README.md b/plugins/Customizer/Assets/css/README.md
new file mode 100644
index 00000000..7b7416b3
--- /dev/null
+++ b/plugins/Customizer/Assets/css/README.md
@@ -0,0 +1,39 @@
+
+Preset Themes that come with Customizer
+--------
+
+<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/Clemson_Tigers_logo.svg/2000px-Clemson_Tigers_logo.svg.png" height="20"> **Clemson Theme (Go Tigers!):**
+
+![image](https://user-images.githubusercontent.com/26339368/48094361-fccc3c80-e1df-11e8-9695-6b9c510aa522.png)
+
+:octocat: Github:
+
+![image](https://user-images.githubusercontent.com/26339368/47761386-8636b880-dc8e-11e8-9b6e-c46e7b5dcc44.png)
+
+:milky_way: Galaxy:
+
+![image](https://user-images.githubusercontent.com/26339368/47761350-68695380-dc8e-11e8-9e87-a9471e5e1adf.png)
+
+
+:partly_sunny: Breathe:
+
+![image](https://user-images.githubusercontent.com/26339368/47761312-47086780-dc8e-11e8-9460-5b1ce4b54d5e.png)
+
+:blue_book: Blueboard
+
+![image](https://user-images.githubusercontent.com/26339368/49310748-29a50400-f4ad-11e8-9734-eca2e5a558fc.png)
+
+:sparkles: Material
+
+![image](https://user-images.githubusercontent.com/26339368/49310723-1003bc80-f4ad-11e8-8c03-8390ecc78d20.png)
+
+:8ball: KindaDark
+
+<img src="https://i.imgur.com/Rg9RYwf.png" />
+<br/>
+
+
+
+## How to add your own css
+
+To add your own custom css files to Customizer, simply place your files in the `DATA_DIR . '/files/customizer/themes` folder. If that folder doesn't exist, create it, and then place your files in there. This will retain your custom themes through upgrades of both Kanboard or Customizer.
diff --git a/plugins/Customizer/Assets/css/customizer.css b/plugins/Customizer/Assets/css/customizer.css
new file mode 100644
index 00000000..9423a4d5
--- /dev/null
+++ b/plugins/Customizer/Assets/css/customizer.css
@@ -0,0 +1,316 @@
+/* KANBOARD PLUGIN - CUSTOMIZER CSS FILE */
+
+/*------ PAGE HEADER ------*/
+.logo > a > img {vertical-align:middle;} /* This aligns the logo certically to the text */
+
+.logo a {
+ opacity:1.0;
+}
+
+/*------ LOGIN PAGE-SPECIFIC STYLES MOVED TO logintop.php SO THEY WON'T AFFECT OTHER PARTS OF KANBOARD ------*/
+
+
+/*------ PLUGIN SETTINGS PAGE ------*/
+.form-help {margin-top: 5px;} /* This gives line spacing above the help text */
+
+code {
+ margin: 0;
+ /*padding: 2px 0.4em;*/
+ background-color: rgba(27, 31, 35, 0.32);
+ border-radius: 3px;
+ color: #FFF;
+ font-family: Tahoma;
+ font-size: 95%!important;
+ letter-spacing: 1px;
+} /* This styles the url link examples */
+
+.login-link-block > label {
+ font-weight: bold;
+ margin-bottom: 5px;
+} /* This is to highlight the title fields */
+
+.panel-heading {
+ margin: -2px -25px;
+ float: right;
+ font-size: 1.2em;
+} /* This adds a heading area to each section */
+
+.panel-title {
+ margin-top: 0;
+ font-weight: bold;
+} /* This is the title text for the heading area */
+
+.links-title {
+ margin: -2px 25px;
+} /* This is the title text for the links heading area, styled uniquely as the type is fieldset */
+
+.upload-link {
+ float: left;
+ list-style: outside none none;
+ width: auto;
+ display: block;
+ margin: 0px 10px;
+} /* This places the upload link on the same line as the remove link */
+
+.remove-link {
+ float: left;
+ list-style: outside none none;
+ width: auto;
+ display: block;
+ margin: 0px 10px;
+} /* This places the upload link on the same line as the remove link */
+
+.upload-link > a > i {
+ color: green;
+} /* This colours the upload icon to green */
+
+.remove-link > a > i {
+ color: red;
+} /* This colours the upload icon to red */
+
+/* Style the buttons that are used to open and close the accordion panel */
+.login-accordion {
+ font-weight: bold;
+ background-color: rgba(136,136,136,0.7);
+ cursor: pointer;
+ padding: 18px;
+ width: 100%;
+ text-align: left;
+ border: none;
+ outline: none;
+ transition: 0.4s;
+ color: #f5f5f5;
+ border-radius: 4px;
+ margin-bottom: 5px;
+ box-shadow: 0 1px 3px 0px rgba(70, 70, 70, 0.10);
+}
+
+/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
+.login-accordion:hover {
+ background-color: #f5f5f5;
+ color: #777777;
+ transition: 0.4s;
+}
+
+.current {
+ background-color: #f5f5f5;
+ color: #777777;
+ transition: 0.4s;
+}
+
+/* Style the accordion panel. Note: hidden by default */
+.login-accordian-panel {
+ max-height: 0;
+ overflow: hidden;
+ transition: 0.4s ease-in-out;
+ opacity: 0;
+}
+
+div.login-accordian-panel.show {
+ opacity: 1;
+ max-height: 5000px;
+}
+
+.title-creator {
+ border-left: 5px solid #333;
+ padding-left: 8px;
+}
+
+/*
+* Short classes
+* m - for classes that set margin
+* p - for classes that set padding
+* t - for classes that set margin-top or padding-top
+* b - for classes that set margin-bottom or padding-bottom
+* l - for classes that set margin-left or padding-left
+* r - for classes that set margin-right or padding-right
+* number(5) - for classes that set the margin or padding
+*/
+
+.mt-5 {
+ margin-top: 5px;
+}
+
+.mt-10 {
+ margin-top: 10px;
+}
+
+.mt-15 {
+ margin-top: 15px;
+}
+
+.mt-20 {
+ margin-top: 20px;
+}
+
+.mb-5 {
+ margin-bottom: 5px;
+}
+
+.mb-10 {
+ margin-bottom: 10px;
+}
+
+.mb-15 {
+ margin-bottom: 15px;
+}
+
+.mb-20 {
+ margin-bottom: 20px;
+}
+
+.ml-5 {
+ margin-left: 5px;
+}
+
+.ml-10 {
+ margin-left: 10px;
+}
+
+.ml-15 {
+ margin-left: 15px;
+}
+
+.ml-20 {
+ margin-left: 20px;
+}
+
+.mr-5 {
+ margin-right: 5px;
+}
+
+.mr-10 {
+ margin-right: 10px;
+}
+
+.mr-15 {
+ margin-right: 15px;
+}
+
+.mr-20 {
+ margin-right: 20px;
+}
+
+.pt-5 {
+ padding-top: 5px;
+}
+
+.pt-10 {
+ padding-top: 10px;
+}
+
+.pt-15 {
+ padding-top: 15px;
+}
+
+.pt-20 {
+ padding-top: 20px;
+}
+
+.pb-5 {
+ padding-bottom: 5px;
+}
+
+.pb-10 {
+ padding-bottom: 10px;
+}
+
+.pb-15 {
+ padding-bottom: 15px;
+}
+
+.pb-20 {
+ padding-bottom: 20px;
+}
+
+.pl-5 {
+ padding-left: 5px;
+}
+
+.pl-10 {
+ padding-left: 10px;
+}
+
+.pl-15 {
+ padding-left: 15px;
+}
+
+.pl-20 {
+ padding-left: 20px;
+}
+
+.pr-5 {
+ padding-right: 5px;
+}
+
+.pr-10 {
+ padding-right: 10px;
+}
+
+.pr-15 {
+ padding-right: 15px;
+}
+
+.pr-20 {
+ padding-right: 20px;
+}
+
+.switch {
+ position: relative;
+ display: inline-block;
+ width: 60px;
+ height: 34px;
+}
+
+.switch input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+
+.slider {
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: #ccc;
+ -webkit-transition: .4s;
+ transition: .4s;
+}
+
+.slider:before {
+ position: absolute;
+ content: "";
+ height: 26px;
+ width: 26px;
+ left: 4px;
+ bottom: 4px;
+ background-color: white;
+ -webkit-transition: .4s;
+ transition: .4s;
+}
+
+input:checked + .slider {
+ background-color: #2196F3;
+}
+
+input:focus + .slider {
+ box-shadow: 0 0 1px #2196F3;
+}
+
+input:checked + .slider:before {
+ -webkit-transform: translateX(26px);
+ -ms-transform: translateX(26px);
+ transform: translateX(26px);
+}
+
+/* Rounded sliders */
+.slider.round {
+ border-radius: 34px;
+}
+
+.slider.round:before {
+ border-radius: 50%;
+}
diff --git a/plugins/Customizer/Assets/css/theme.css b/plugins/Customizer/Assets/css/theme.css
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/plugins/Customizer/Assets/css/theme.css
@@ -0,0 +1 @@
+
diff --git a/plugins/Customizer/Assets/css/themes/Blueboard.css b/plugins/Customizer/Assets/css/themes/Blueboard.css
new file mode 100644
index 00000000..1fa8097b
--- /dev/null
+++ b/plugins/Customizer/Assets/css/themes/Blueboard.css
@@ -0,0 +1,265 @@
+/*!
+ * Blueboard by bgibout - Theme for Kanboard
+ * Licensed under the MIT license - blueboard/LICENSE
+ * https://github.com/bgibout/blueboard
+ * Copyright (c) 2017 bgibout
+ */
+
+body {
+ color: #868ba1;
+ background-color: #e9ecef;
+ font-family: "Roboto", "Helvetica Neue", Arial, sans-s
+}
+
+.page {
+ margin: 0;
+}
+
+header {
+ margin: 0 !important;
+ border: 0 !important;
+ background-color : #3B6998 !important;
+}
+
+.action-menu {
+ padding: 10px;
+}
+
+.views {
+ line-height: 30px;
+}
+
+.filter-box-component {
+ padding-top: 5px;
+}
+
+.project-header .dropdown-component {
+ margin-top: 4px;
+ margin-right: 5px;
+ float: left;
+ line-height: 30px;
+}
+
+header h1 {
+ color: #ffffff;
+ font-weight: 600;
+ font-size: 1.1rem;
+ line-height: 2rem
+}
+
+.tooltip i.fa {
+ color: #fff;
+}
+
+.btn {
+ text-decoration: none;
+ border-radius: 0;
+ min-height: 30px;
+ line-height: 30px;
+ color: #fff;
+ background-color: #3B6998;
+ border-color: #386491;
+ font-size: 12px;
+ text-align: center;
+ padding-left: 15px;
+ padding-right: 15px
+}
+
+label {
+ padding: 0px 40px 0 0px;
+ line-height: 25px;
+ vertical-align: middle;
+ font-size: .9rem;
+}
+
+/*
+ form {
+ margin-top: 40px;
+}
+*/
+
+fieldset {
+ background: #ffffff;
+}
+
+select, span.select2 {
+ width: 95% !important;
+}
+
+input[type="number"], input[type="date"], input[type="email"], input[type="password"], input[type="text"]:not(.input-addon-field) {
+ padding: 0.65rem 0.75rem;
+ font-size: 0.875rem;
+ line-height: 1.25;
+ color: #495057;
+ background-color: #fff;
+ background-image: none;
+ background-clip: padding-box;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ border-radius: 2px;
+ transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+ height: auto
+}
+
+.select-dropdown-input {
+ border: none !important;
+}
+
+.select-dropdown-input-container .select-dropdown-chevron {
+ top: 10px;
+}
+
+.js-submit-buttons-rendered {
+ margin-top: 40px;
+}
+
+.page .page-header {
+ padding: 10px;
+ background: #343a40;
+ font-size: 0.9rem;
+ margin: 0;
+}
+
+.page-header a {
+ color: #ffffff;
+ text-decoration: none;
+}
+
+#config-section .page-header a {
+ color: #000;
+ font-size: 0.8rem;
+}
+
+.page-header a .fa {
+ color: #868e96;
+}
+
+.menus-container a .fa {
+ color: #ffffff;
+}
+
+.margin-bottom {
+ margin-bottom: 20px;
+ padding: 10px;
+}
+
+.accordion-section {
+ margin: 20px;
+ background: #fff;
+ padding: 50px 30px;
+}
+
+.project-overview-column {
+ background: #fff;
+}
+
+.table-list {
+ font-size: 0.85em;
+ margin-bottom: 20px;
+ padding: 10px;
+ background: #ffffff;
+}
+
+.table-list-header {
+ background: #fbfbfb;
+ border: 1px solid #e5e5e5;
+ border-radius: 5px 5px 0 0;
+ line-height: 30px;
+ padding-left: 10px;
+ padding-right: 3px;
+ height: 30px;
+}
+
+.table-list-row {
+ padding: 10px;
+}
+
+.sidebar-content {
+ background: #f8f9fa;
+ margin-top: 10px;
+ padding: 10px;
+}
+
+.sidebar-container {
+ height: 100%;
+ width: 100%;
+}
+
+.sidebar-container .page-header {
+ background: none;
+}
+
+.sidebar {
+ margin-top: 20px;
+}
+
+.sidebar .js-select-dropdown-autocomplete-rendered {
+ padding: 10px;
+}
+
+
+.sidebar ul {
+ margin-top: 10px;
+}
+
+.sidebar ul:before {
+ content: "Navigation";
+ text-transform: uppercase;
+ font-size: 11px;
+ padding: 7px 15px;
+ display: block;
+ margin-bottom: 0;
+ font-weight: 500;
+ letter-spacing: 0.5px;
+}
+
+.sidebar ul>li {
+ padding: 5px 0 0 15px;
+ border-top: 1px solid #ced4da;
+}
+
+.sidebar ul>li:last-child {
+ border-bottom: 1px solid #ced4da;
+}
+
+.sidebar ul>li a {
+ color: #868e96;
+ font-size: 0.9rem;
+}
+
+.sidebar>ul li.active {
+ background-color: #f8f9fa
+}
+
+.sidebar ul>li.active a {
+ color: #343a40;
+}
+
+#modal-box {
+ padding: 20px 20px 30px 20px;
+}
+
+table {
+ color: #000;
+}
+
+table.table-fixed th {
+ background: #3B6998;
+}
+table.table-fixed th, table.table-fixed th a {
+ color: #ffffff ;
+}
+
+table.table-fixed th, table.table-fixed td {
+ padding-left: 10px;
+}
+
+.board-add-icon i {
+ color: #3B6998;
+}
+
+
+@media (max-width: 768px) {
+ .sidebar {
+ max-width: 100%;
+ }
+}
diff --git a/plugins/Customizer/Assets/css/themes/Breathe.css b/plugins/Customizer/Assets/css/themes/Breathe.css
new file mode 100644
index 00000000..b262244c
--- /dev/null
+++ b/plugins/Customizer/Assets/css/themes/Breathe.css
@@ -0,0 +1,327 @@
+/*!
+ * Modified version of Oxygen - Theme for Kanboard
+ * Licensed under the MIT license - Oxygen/LICENSE
+ * https://github.com/kenlog/Oxygen
+ * Copyright (c) 2018 Valentino Pesce - https://iltuobrand.it
+ */
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+}
+h1{
+ color:#007BA8;
+}
+a{
+ color:#007BA8;
+}
+.js-modal-medium{
+ color:#007BA8;
+}
+.sidebar>ul a:hover{
+ color:#007BA8;
+}
+div.task-board-recent {
+ box-shadow: none;
+ border-bottom: 6px solid rgba(0, 0, 0, 0.3);
+}
+div.ganttview-vtheader-series-name {
+ padding: 0 6px;
+}
+th,td {
+ padding: 10px;
+}
+header {
+ border-bottom: none;
+ box-shadow: 0px 1px 3px 0 rgba(46,61,73,.12);
+ padding: 15px 10px;
+ margin-bottom: 15px;
+ background-color: #e8fbff!important;
+}
+.header img {
+ float: left;
+}
+.header h2 {
+ position: relative;
+ color:#007BA8;
+ top: 13px;
+ left: 10px;
+ margin: 0;
+}
+label {
+ font-weight: bold;
+ margin-top: 18px;
+}
+.task-board {
+ margin-bottom: 8px;
+ padding: 12px;
+ border-radius: 6px;
+ box-shadow: 0px 5px 5px 0 rgba(46,61,73,.12);
+ border: none;
+}
+.task-board-title {
+ font-size: 1.25em;
+ font-weight: 600;
+}
+.task-show-details {
+ border-radius: 10px;
+ margin-bottom: 20px;
+}
+.task-show-details h2 {
+ background-color: rgba(0, 0, 0, 0.3);
+ padding: 20px;
+ border-radius: 8px 8px 0 0;
+ color: #fff;
+}
+.task-show-details ul {
+ padding: 20px;
+}
+.task-summary-container {
+ border: none;
+ border-radius: 7px;
+ border-bottom: 5px solid;
+ padding: 20px;
+}
+.table-small {
+ font-size: 1em;
+}
+table th {
+ text-align: left;
+ padding: 0.7em 3px;
+ border: 1px solid #eee;
+ background: #fbfbfb;
+}
+table td {
+ border: none;
+ padding: 0.5em 7px;
+ vertical-align: top;
+}
+.sidebar ul {
+ padding-bottom: 20px;
+ border-bottom: 1px solid #ddd;
+}
+.sidebar ul:last-of-type {
+ border-bottom: none;
+}
+.sidebar-collapse i,.sidebar-collapsed .sidebar {
+ background-color: #999;
+ border-radius: 24px;
+ padding: 5px 10px 5px 8px;
+}
+.sidebar-collapse i,.sidebar-collapsed .sidebar i {
+ color: #fff;
+}
+.sidebar-collapse i:hover,.sidebar-collapsed .sidebar:hover {
+ background-color: #ccc;
+}
+.page-header {
+ margin: 15px 0;
+ background-color: #eee;
+ padding: 6px 10px 10px 10px;
+ border-bottom: 1px solid #ddd;
+ border-top: 1px solid #ddd;
+}
+.page-header li {
+ font-size: 1.1em;
+}
+.page-header h2 {
+ border-bottom: none;
+}
+.listing {
+ border: none;
+}
+.fa-play {
+ font-size: 1.2em;
+ background-color: #ddd;
+ border-radius: 100%;
+ width: 25px;
+ height: 28px;
+ padding: 8px 0 0 15px;
+ margin-right: 8px;
+}
+.fa-play:hover {
+ background-color: #999;
+ color: #fff;
+}
+.fc-event .fc-content {
+ padding: 4px 8px;
+}
+.board-add-icon {
+ float: left;
+ padding: 0 5px
+}
+.board-add-icon i {
+ text-decoration: none;
+ color: #289E7B;
+ font-size: 1.4em
+}
+.board-add-icon i:focus,
+.board-add-icon i:hover {
+ text-decoration: none;
+ color: #23292d
+}
+.form-column select {
+ font-size: 1.2em;
+ margin: 10px 0 2px 0;
+}
+.form-column:nth-of-type(2) {
+ padding: 10px 25px;
+ background-color: #f7f7f7;
+}
+.form-actions {
+ font-size: 1.2em;
+}
+.page-header ul.dropdown-submenu-open {
+ margin: 10px 0 0 -10px;
+ padding: 0;
+}
+.select-dropdown-input-container {
+ max-width: 300px;
+}
+.dropdown-submenu-open li:nth-child(even) {
+ background-color: #f7f7f7;
+}
+.dropdown-submenu-open li {
+ padding: 6px 10px;
+}
+.dropdown-submenu-open li:hover {
+ background-color: #eee;
+}
+.filters {
+ border: none;
+}
+.filters ul.dropdown-submenu-open {
+ margin: 6px 0 0 8px;
+}
+.markdown pre {
+ background: #3333330d;
+ max-height: 600px;
+ overflow: auto;
+ margin-bottom: 1em;
+ padding: 5px;
+ color: #242729;
+ font-family: Consolas,Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,sans-serif;
+}
+a i.web-notification-icon {
+ color: #D45353;
+}
+.fa-play {
+ font-size:1.2em;
+ background-color: #ddd;
+ border-radius: 100%;
+ width: 25px;
+ height: 25px!important;
+ padding: 9px 3px 0 9px!important;
+ margin-right: 8px;
+}
+#modal-box {
+ padding: 10px;
+}
+.table-list-category {
+ padding: 2px 2px 2px 5px;
+}
+.table-list-row:hover {
+ background: #f5f5f5;
+ border-bottom: 1px solid #f7f7f7;
+ border-right: 1px solid #f7f7f7;
+}
+.page-header {
+ margin: 15px 0;
+ background-color: #ffffff;
+ padding: 6px 10px 10px 10px;
+ border-bottom: 1px solid #f7f7f7;
+ border-top: 1px solid #f7f7f7;
+}
+.comments .comment-highlighted {
+ background-color: #fff;
+ border: 2px solid #F5E982;
+ border-radius: 5px;
+}
+.comments .comment:hover {
+ background: #f5f5f5;
+ border-radius: 5px;
+}
+.comments .comment:nth-child(even):not(.comment-highlighted):hover {
+ border-radius: 5px;
+ background: #f5f5f5;
+}
+.comments .comment:nth-child(even):not(.comment-highlighted) {
+ background: #fbfbfb;
+ border-radius: 5px;
+}
+.sidebar {
+ background-color: #f7f7f7;
+ padding: 7px;
+ border-radius: 7px;
+ border-left: 2px solid #e9e9e9;
+ box-shadow: 1px 0px 7px 0 rgba(46,61,73,.12);
+}
+input[type="number"]:focus, input[type="date"]:focus, input[type="email"]:focus, input[type="password"]:focus, input[type="text"]:focus {
+ color: #000;
+ border-color: #007BA8;
+ outline: 0;
+ box-shadow: 0 0 0 0.2rem rgba(222,222,222,0.25);
+}
+textarea:focus {
+ color: #000;
+ border-color: #007BA8;
+ outline: 0;
+ box-shadow: 0 0 0 0.2rem rgba(222,222,222,0.25);
+}
+input[type="number"], input[type="date"], input[type="email"], input[type="password"], input[type="text"]:not(.input-addon-field) {
+ padding:3px;
+ transition: box-shadow 1s;
+}
+.text-editor textarea{
+ padding:3px;
+ transition: box-shadow 1s;
+}
+select{
+ border: 1px solid #ccc;
+ background: #f9f9f9;
+ padding: 3px;
+}
+.btn-blue {
+ border-color: #007BA8;
+ background: #007BA8;
+ color: #fff;
+ background-image: linear-gradient(-180deg, #007BA8 0%, #007387 90%);
+}
+.btn-blue:hover, .btn-blue:focus {
+ border-color: #007BA8;
+ background: #ffffff;
+ color: #007BA8;
+}
+.select2-container--default.select2-container--focus .select2-selection--multiple {
+ border: solid #007BA8 1px;
+ outline: 0;
+}
+.select2-container--default .select2-results__option--highlighted[aria-selected] {
+ background-color: #007BA8;
+ color: white;
+}
+.board-add-icon i:focus, .board-add-icon i:hover {
+ text-decoration: none;
+ color: #91C259;
+}
+.board-add-icon i {
+ text-decoration: none;
+ color: #007BA8;
+ font-size: 1.4em;
+}
+.dropdown-submenu-open li:not(.no-hover):hover {
+ background: #007BA8;
+ color: #fff;
+}
+.sidebar>ul li.active a {
+ color: #007BA8;
+ font-weight: bold;
+}
+.table-list-row .table-list-title a:hover, .table-list-row .table-list-title a:focus {
+ text-decoration: underline;
+ color: #007BA8;
+}
+.image-slideshow-overlay img {
+ display: block;
+ margin: auto;
+ max-width: 100%;
+}
diff --git a/plugins/Customizer/Assets/css/themes/Clemson.css b/plugins/Customizer/Assets/css/themes/Clemson.css
new file mode 100644
index 00000000..d7406315
--- /dev/null
+++ b/plugins/Customizer/Assets/css/themes/Clemson.css
@@ -0,0 +1,341 @@
+/*!
+ * Modified version of Oxygen - Theme for Kanboard
+ * Licensed under the MIT license - Oxygen/LICENSE
+ * https://github.com/kenlog/Oxygen
+ * Copyright (c) 2018 Valentino Pesce - https://iltuobrand.it
+ */
+
+@import url("https://fonts.googleapis.com/css?family=Raleway:400,700");
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+}
+
+h1{
+ color:#FF6200;
+}
+a{
+ color:#FF6200;
+}
+.js-modal-medium{
+ color:#FF6200;
+}
+.sidebar>ul a:hover{
+ color:#FF6200;
+}
+div.ganttview-vtheader-series-name {
+ padding: 0 6px;
+}
+th,td {
+ padding: 10px;
+}
+header {
+ border-bottom: none;
+ box-shadow: 0px 1px 3px 0 rgba(46,61,73,.12);
+ padding: 15px 10px;
+ margin-bottom: 15px;
+ background-color: #ff6200; /* Old browsers */
+ background: -moz-linear-gradient(-45deg, #ff6200 36%, #9d00ff 100%); /* FF3.6-15 */
+ background: -webkit-linear-gradient(-45deg, #ff6200 36%,#9d00ff 100%); /* Chrome10-25,Safari5.1-6 */
+ background: linear-gradient(135deg, #ff6200 36%,#9d00ff 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ff6200', endColorstr='#9d00ff',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
+}
+.header img {
+ float: left;
+}
+.header h2 {
+ position: relative;
+ color:#FFFFFF;!important;
+ top: 13px;
+ left: 10px;
+ margin: 0;
+}
+header h1 {
+ font-size: 1.4em!important;
+ color: #FFFFFF!important;
+}
+label {
+ font-weight: bold;
+ margin-top: 18px;
+}
+
+.task-board {
+ margin-bottom: 8px;
+ padding: 9px;
+ border-radius: 3px;
+ box-shadow: 0px 1px 4px 0.6px rgba(157,0,255,0.6);
+ border: 2px solid;
+ background-color: #ffffff!important;
+}
+.task-board-title {
+ font-size: 1.25em;
+ font-weight: 600;
+}
+.task-show-details {
+ border-radius: 10px;
+ margin-bottom: 20px;
+}
+.task-show-details h2 {
+ background-color: rgba(0, 0, 0, 0.3);
+ padding: 20px;
+ border-radius: 8px 8px 0 0;
+ color: #fff;
+}
+.task-show-details ul {
+ padding: 20px;
+}
+.task-summary-container {
+ border: none;
+ border-radius: 7px;
+ border-bottom: 5px solid;
+ padding: 20px;
+}
+.table-small {
+ font-size: 1em;
+}
+table th {
+ text-align: left;
+ padding: 0.7em 3px;
+ border: 1px solid #F2DCD3;
+ background: #FFEFEB;
+}
+table td {
+ border: none;
+ padding: 0.5em 7px;
+ vertical-align: top;
+}
+.sidebar ul {
+ padding-bottom: 20px;
+ border-bottom: 1px solid #ddd;
+}
+.sidebar ul:last-of-type {
+ border-bottom: none;
+}
+.sidebar-collapse i,.sidebar-collapsed .sidebar {
+ background-color: #999;
+ border-radius: 24px;
+ padding: 5px 10px 5px 8px;
+}
+.sidebar-collapse i,.sidebar-collapsed .sidebar i {
+ color: #fff;
+}
+.sidebar-collapse i:hover,.sidebar-collapsed .sidebar:hover {
+ background-color: #ccc;
+}
+.page-header {
+ margin: 15px 0;
+ background-color: #F2DCD3;
+ padding: 6px 10px 10px 10px;
+ border-bottom: 1px solid #ddd;
+ border-top: 1px solid #ddd;
+}
+.page-header li {
+ font-size: 1.1em;
+}
+.page-header h2 {
+ border-bottom: none;
+}
+.listing {
+ border: none;
+}
+.fa-play {
+ font-size: 1.2em;
+ background-color: #ddd;
+ border-radius: 100%;
+ width: 25px;
+ height: 28px;
+ padding: 8px 0 0 15px;
+ margin-right: 8px;
+}
+.fa-play:hover {
+ background-color: #999;
+ color: #fff;
+}
+.fc-event .fc-content {
+ padding: 4px 8px;
+}
+.board-add-icon {
+ float: left;
+ padding: 0 5px
+}
+.board-add-icon i {
+ text-decoration: none;
+ color: #9D00FF;
+ font-size: 1.4em
+}
+.board-add-icon i:focus,
+.board-add-icon i:hover {
+ text-decoration: none;
+ color: #23292d
+}
+.form-column select {
+ font-size: 1.2em;
+ margin: 10px 0 2px 0;
+}
+.form-column:nth-of-type(2) {
+ padding: 10px 25px;
+ background-color: #f7f7f7;
+}
+.form-actions {
+ font-size: 1.2em;
+}
+.page-header ul.dropdown-submenu-open {
+ margin: 10px 0 0 -10px;
+ padding: 0;
+}
+.select-dropdown-input-container {
+ max-width: 300px;
+}
+.dropdown-submenu-open li:nth-child(even) {
+ background-color: #f7f7f7;
+}
+.dropdown-submenu-open li {
+ padding: 6px 10px;
+}
+.dropdown-submenu-open li:hover {
+ background-color: #F2DCD3;
+}
+.filters {
+ border: none;
+}
+.filters ul.dropdown-submenu-open {
+ margin: 6px 0 0 8px;
+}
+.markdown pre {
+ background: #FFF;
+ max-height: 600px;
+ overflow: auto;
+ margin-bottom: 1em;
+ padding: 5px;
+ color: #242729;
+ font-family: Consolas,Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,sans-serif;
+}
+code {
+ background-color: rgba(249, 161, 0, 0.12)!important;
+ color: #ab05ff!important;
+}
+a i.web-notification-icon {
+ color: #FF6200;
+}
+.fa-play {
+ font-size:1.2em;
+ background-color: #ddd;
+ border-radius: 100%;
+ width: 25px;
+ height: 25px!important;
+ padding: 9px 3px 0 9px!important;
+ margin-right: 8px;
+}
+#modal-box {
+ padding: 10px;
+}
+.table-list-category {
+ padding: 2px 2px 2px 5px;
+}
+.table-list-row:hover {
+ background: #f5f5f5;
+ border-bottom: 1px solid #f7f7f7;
+ border-right: 1px solid #f7f7f7;
+}
+.page-header {
+ margin: 15px 0;
+ background-color: #ffffff;
+ padding: 6px 10px 10px 10px;
+ border-bottom: 1px solid #f7f7f7;
+ border-top: 1px solid #f7f7f7;
+}
+.comments .comment-highlighted {
+ background-color: #fff;
+ border: 2px solid #AD3BFF;
+ border-radius: 5px;
+}
+.comments .comment:hover {
+ background: #f5f5f5;
+ border-radius: 5px;
+}
+.comments .comment:nth-child(even):not(.comment-highlighted):hover {
+ border-radius: 5px;
+ background: #f5f5f5;
+}
+.comments .comment:nth-child(even):not(.comment-highlighted) {
+ background: #FFEFEB;
+ border-radius: 5px;
+}
+.sidebar {
+ background-color: #f7f7f7;
+ padding: 7px;
+ border-radius: 7px;
+ border-left: 2px solid #e9e9e9;
+ box-shadow: 1px 0px 7px 0 rgba(46,61,73,.12);
+}
+input[type="number"]:focus, input[type="date"]:focus, input[type="email"]:focus, input[type="password"]:focus, input[type="text"]:focus {
+ color: #000;
+ border-color: #FF6200;
+ outline: 0;
+ box-shadow: 0 0 0 0.2rem rgba(222,222,222,0.25);
+}
+textarea:focus {
+ color: #000;
+ border-color: #FF6200;
+ outline: 0;
+ box-shadow: 0 0 0 0.2rem rgba(222,222,222,0.25);
+}
+input[type="number"], input[type="date"], input[type="email"], input[type="password"], input[type="text"]:not(.input-addon-field) {
+ padding:3px;
+ transition: box-shadow 1s;
+}
+.text-editor textarea{
+ padding:3px;
+ transition: box-shadow 1s;
+}
+select{
+ border: 1px solid #ccc;
+ background: #f9f9f9;
+ padding: 3px;
+}
+.btn-blue {
+ border-color: #FF6200;
+ background: #FF6200;
+ color: #fff;
+ background-image: linear-gradient(-180deg, #FF6200 0%, #4B00B5 90%);
+}
+.btn-blue:hover, .btn-blue:focus {
+ border-color: #FF6200;
+ background: #ffffff;
+ color: #FF6200;
+}
+.select2-container--default.select2-container--focus .select2-selection--multiple {
+ border: solid #FF6200 1px;
+ outline: 0;
+}
+.select2-container--default .select2-results__option--highlighted[aria-selected] {
+ background-color: #FF6200;
+ color: white;
+}
+.board-add-icon i:focus, .board-add-icon i:hover {
+ text-decoration: none;
+ color: #E68332;
+}
+.board-add-icon i {
+ text-decoration: none;
+ color: #FF6200;
+ font-size: 1.4em;
+}
+.dropdown-submenu-open li:not(.no-hover):hover {
+ background: #FF6200;
+ color: #fff;
+}
+.sidebar>ul li.active a {
+ color: #FF6200;
+ font-weight: bold;
+}
+.table-list-row .table-list-title a:hover, .table-list-row .table-list-title a:focus {
+ text-decoration: underline;
+ color: #FF6200;
+}
+.image-slideshow-overlay img {
+ display: block;
+ margin: auto;
+ max-width: 100%;
+}
+
diff --git a/plugins/Customizer/Assets/css/themes/Galaxy.css b/plugins/Customizer/Assets/css/themes/Galaxy.css
new file mode 100644
index 00000000..9517cecc
--- /dev/null
+++ b/plugins/Customizer/Assets/css/themes/Galaxy.css
@@ -0,0 +1,2713 @@
+/*!
+ * Modified version of Nebula - Theme for Kanboard
+ * Licensed under the MIT license - Nebula/LICENSE
+ * https://github.com/kenlog/Nebula
+ * Copyright (c) 2018 Valentino Pesce - https://iltuobrand.it
+ */
+
+ h1,li,ul,ol,table,tr,td,th,p,blockquote,body {
+ margin:0;
+ padding:0;
+ font-size:100%
+}
+body {
+ padding-bottom:10px;
+ color:#ced4da;
+ background-color: #222;
+ font-family: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+ text-rendering:optimizeLegibility
+}
+code {
+ background-color: #cccccc52!important;
+}
+small {
+ font-size:0.8em
+}
+::selection{background:rgba(113,113,113,0.5);color:#ffffff}::-moz-selection{background:rgba(113,113,113,0.5);color:#ffffff}
+hr {
+ border:0;
+ height:0;
+ border-top:0px solid rgba(0,0,0,0.1);
+ border-bottom:0px solid rgba(255,255,255,0.3)
+}
+::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
+ color: #ffffff!important;
+ opacity: 1; /* Firefox */
+}
+
+:-ms-input-placeholder { /* Internet Explorer 10-11 */
+ color: #ffffff!important;
+}
+
+::-ms-input-placeholder { /* Microsoft Edge */
+ color: #ffffff!important;
+}
+.select-dropdown-input-container {
+ background-color: #252525!important;
+ max-width: 300px;
+}
+.page {
+ margin-left:10px;
+ margin-right:10px
+}
+.margin-top {
+ margin-top:20px
+}
+.margin-bottom {
+ margin-bottom:20px
+}
+.pull-right {
+ text-align:right
+}
+ul.no-bullet li {
+ list-style-type:none;
+ margin-left:0
+}
+#app-loading-icon {
+ position:fixed;
+ right:3px;
+ bottom:3px
+}
+.assign-me {
+ vertical-align:bottom
+}
+a {
+ color:#00bc8c;
+ border:none;
+ text-decoration:none;
+}
+a:focus {
+ outline:0;
+ color:#20c997;
+ text-decoration:none
+}
+a:hover {
+ color:#ced4da;
+ text-decoration:none
+}
+a .fa {
+ padding-right:3px;
+ text-decoration:none;
+ color:#ced4da
+}
+h1,h2,h3 {
+ font-weight:normal;
+ color:#ffffff
+}
+h1 {
+ font-size:1.5em
+}
+h2 {
+ font-size:1.4em;
+ margin-bottom:10px
+}
+h3 {
+ margin-top:10px;
+ font-size:1.2em
+}
+table {
+ width:100%;
+ border-collapse:collapse;
+ border-spacing:0;
+ margin-bottom:20px
+}
+table.table-fixed {
+ table-layout:fixed;
+ white-space:nowrap
+}
+table.table-fixed th {
+ overflow:hidden
+}
+table.table-fixed td {
+ white-space:nowrap;
+ overflow:hidden;
+ text-overflow:ellipsis
+}
+table.table-small {
+ font-size:0.8em
+}
+table.table-striped tr:nth-child(odd) {
+ background:#252525
+}
+@media (max-width: 768px) {
+ table.table-scrolling {
+ overflow-x:auto;
+ display:inline-block;
+ vertical-align:top;
+ max-width:100%;
+ white-space:nowrap
+ }
+}
+table th {
+ text-align:left;
+ padding:0.5em 3px;
+ border:0px solid #eee;
+ background:#252525
+}
+table th a {
+ text-decoration:none;
+ color:#ced4da
+}
+table th a:focus,table th a:hover {
+ text-decoration:underline
+}
+table td {
+ border:0px solid #eee;
+ padding:0.5em 3px;
+ vertical-align:top
+}
+table td li {
+ margin-left:20px
+}
+.column-1 {
+ width:1%
+}
+.column-2 {
+ width:2%
+}
+.column-3 {
+ width:3%
+}
+.column-4 {
+ width:4%
+}
+.column-5 {
+ width:5%
+}
+.column-6 {
+ width:6%
+}
+.column-7 {
+ width:7%
+}
+.column-8 {
+ width:8%
+}
+.column-9 {
+ width:9%
+}
+.column-10 {
+ width:10%
+}
+.column-11 {
+ width:11%
+}
+.column-12 {
+ width:12%
+}
+.column-13 {
+ width:13%
+}
+.column-14 {
+ width:14%
+}
+.column-15 {
+ width:15%
+}
+.column-16 {
+ width:16%
+}
+.column-17 {
+ width:17%
+}
+.column-18 {
+ width:18%
+}
+.column-19 {
+ width:19%
+}
+.column-20 {
+ width:20%
+}
+.column-21 {
+ width:21%
+}
+.column-22 {
+ width:22%
+}
+.column-23 {
+ width:23%
+}
+.column-24 {
+ width:24%
+}
+.column-25 {
+ width:25%
+}
+.column-26 {
+ width:26%
+}
+.column-27 {
+ width:27%
+}
+.column-28 {
+ width:28%
+}
+.column-29 {
+ width:29%
+}
+.column-30 {
+ width:30%
+}
+.column-31 {
+ width:31%
+}
+.column-32 {
+ width:32%
+}
+.column-33 {
+ width:33%
+}
+.column-34 {
+ width:34%
+}
+.column-35 {
+ width:35%
+}
+.column-36 {
+ width:36%
+}
+.column-37 {
+ width:37%
+}
+.column-38 {
+ width:38%
+}
+.column-39 {
+ width:39%
+}
+.column-40 {
+ width:40%
+}
+.column-41 {
+ width:41%
+}
+.column-42 {
+ width:42%
+}
+.column-43 {
+ width:43%
+}
+.column-44 {
+ width:44%
+}
+.column-45 {
+ width:45%
+}
+.column-46 {
+ width:46%
+}
+.column-47 {
+ width:47%
+}
+.column-48 {
+ width:48%
+}
+.column-49 {
+ width:49%
+}
+.column-50 {
+ width:50%
+}
+.column-51 {
+ width:51%
+}
+.column-52 {
+ width:52%
+}
+.column-53 {
+ width:53%
+}
+.column-54 {
+ width:54%
+}
+.column-55 {
+ width:55%
+}
+.column-56 {
+ width:56%
+}
+.column-57 {
+ width:57%
+}
+.column-58 {
+ width:58%
+}
+.column-59 {
+ width:59%
+}
+.column-60 {
+ width:60%
+}
+.column-61 {
+ width:61%
+}
+.column-62 {
+ width:62%
+}
+.column-63 {
+ width:63%
+}
+.column-64 {
+ width:64%
+}
+.column-65 {
+ width:65%
+}
+.column-66 {
+ width:66%
+}
+.column-67 {
+ width:67%
+}
+.column-68 {
+ width:68%
+}
+.column-69 {
+ width:69%
+}
+.column-70 {
+ width:70%
+}
+.column-71 {
+ width:71%
+}
+.column-72 {
+ width:72%
+}
+.column-73 {
+ width:73%
+}
+.column-74 {
+ width:74%
+}
+.column-75 {
+ width:75%
+}
+.column-76 {
+ width:76%
+}
+.column-77 {
+ width:77%
+}
+.column-78 {
+ width:78%
+}
+.column-79 {
+ width:79%
+}
+.column-80 {
+ width:80%
+}
+.column-81 {
+ width:81%
+}
+.column-82 {
+ width:82%
+}
+.column-83 {
+ width:83%
+}
+.column-84 {
+ width:84%
+}
+.column-85 {
+ width:85%
+}
+.column-86 {
+ width:86%
+}
+.column-87 {
+ width:87%
+}
+.column-88 {
+ width:88%
+}
+.column-89 {
+ width:89%
+}
+.column-90 {
+ width:90%
+}
+.column-91 {
+ width:91%
+}
+.column-92 {
+ width:92%
+}
+.column-93 {
+ width:93%
+}
+.column-94 {
+ width:94%
+}
+.column-95 {
+ width:95%
+}
+.column-96 {
+ width:96%
+}
+.column-97 {
+ width:97%
+}
+.column-98 {
+ width:98%
+}
+.column-99 {
+ width:99%
+}
+.column-100 {
+ width:100%
+}
+.draggable-row-handle {
+ cursor:move;
+ color: #ced4da;
+ padding-left: 10px;
+}
+.draggable-row-handle:hover {
+ color:#ced4da
+}
+.ui-widget-content {
+ color: #00bc8c;
+}
+tr.draggable-item-selected {
+ background:#fff;
+ border:2px solid #666;
+ box-shadow:4px 2px 10px -4px rgba(0,0,0,0.55)
+}
+tr.draggable-item-selected td {
+ border-top:none;
+ border-bottom:none
+}
+tr.draggable-item-selected td:first-child {
+ border-left:none
+}
+tr.draggable-item-selected td:last-child {
+ border-right:none
+}
+.table-stripped tr.draggable-item-hover,.table-stripped tr.draggable-item-hover {
+ background:#FEFFF2
+}
+.table-list {
+ font-size:0.85em;
+ margin-bottom:20px
+}
+.table-list-header {
+ background:#222;
+ border:0px solid #e5e5e5;
+ border-radius:5px 5px 0 0;
+ line-height:28px;
+ padding-left:3px;
+ padding-right:3px
+}
+.table-list-header a {
+ color:#ced4da;
+ font-weight:500;
+ text-decoration:none;
+ margin-right:10px
+}
+.table-list-header a:hover,.table-list-header a:focus {
+ color:#767676
+}
+.table-list-header .table-list-header-count {
+ color:#ced4d1;
+ display:inline-block;
+ float:left
+}
+.table-list-header .table-list-header-menu {
+ text-align:right
+}
+.table-list-row {
+ padding-left:3px;
+ padding-right:3px;
+ padding: 10px;
+ border-bottom:0px solid #e5e5e5;
+ border-right:0px solid #e5e5e5
+}
+.table-list-row.table-border-left {
+ border-left:0px solid #e5e5e5
+}
+.table-list-row:nth-child(odd) {
+ background:#252525
+}
+.table-list-row:last-child {
+ border-radius:0 0 5px 5px
+}
+.table-list-row:hover {
+ background:#252525;
+ border-bottom:0px solid #ffeb8e;
+ border-right:0px solid #ffeb8e
+}
+.table-list-row .table-list-title {
+ font-weight:500;
+ line-height:23px
+}
+.table-list-row .table-list-title.status-closed {
+ text-decoration:line-through;
+ margin-right:10px
+}
+.table-list-row .table-list-title.status-closed a {
+ font-style:italic
+}
+.table-list-row .table-list-title a {
+ color:#ced4da;
+ text-decoration:none
+}
+.table-list-row .table-list-title a:hover,.table-list-row .table-list-title a:focus {
+ text-decoration:underline
+}
+.table-list-row .table-list-details {
+ color:#f7f7f7;
+ font-weight:300;
+ line-height:20px
+}
+.table-list-row .table-list-details span {
+ margin-left:5px
+}
+.table-list-row .table-list-details span:first-child {
+ margin-left:0
+}
+.table-list-row .table-list-details li {
+ display:inline;
+ list-style-type:none
+}
+.table-list-row .table-list-details li:after {
+ content:', '
+}
+.table-list-row .table-list-details li:last-child:after {
+ content:''
+}
+.table-list-row .table-list-details strong {
+ font-weight:400;
+ color:#f7f7f7
+}
+.table-list-row .table-list-details-with-icons {
+ float:left
+}
+@media (max-width: 768px) {
+ .table-list-row .table-list-details-with-icons {
+ float:none
+ }
+}
+.table-list-row .table-list-icons {
+ font-size:0.8em;
+ text-align:right;
+ line-height:30px
+}
+@media (max-width: 768px) {
+ .table-list-row .table-list-icons {
+ text-align:left;
+ line-height:20px
+ }
+}
+.table-list-row .table-list-icons span {
+ margin-left:5px
+}
+.table-list-row .table-list-icons a {
+ text-decoration:none
+}
+.table-list-row .table-list-icons a:hover {
+ color:#ced4da
+}
+.table-list-row .table-list-icons a:hover i {
+ color:#ced4da
+}
+.table-list-category {
+ font-size:0.9em;
+ font-weight:500;
+ color:#000;
+ padding:1px 2px 1px 2px;
+ border-radius:3px;
+ background:#fcfcfc;
+ border:0px solid #ccc
+}
+.table-list-category a {
+ text-decoration:none;
+ color:#000
+}
+.table-list-category a:hover {
+ color:#00bc8c
+}
+fieldset {
+ border:0px solid #ddd;
+ margin-top:10px
+}
+legend {
+ font-weight:500;
+ font-size:1.2em
+}
+label {
+ cursor:pointer;
+ display:block;
+ margin-top:10px;
+ font-weight:400
+}
+input[type="number"],input[type="date"],input[type="email"],input[type="password"],input[type="text"]:not(.input-addon-field) {
+ width:300px;
+ max-width:95%;
+ font-size:1em;
+ height:25px;
+ padding-bottom:0;
+ padding-left:4px;
+
+ line-height: 1.5;
+ color: #fff;
+ background-color: #252525;
+ border: 2px solid #4a5368;
+ border-radius: .25rem;
+ -webkit-transition: border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;
+ transition: border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;
+ transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
+ transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;
+
+ font-family:sans-serif;
+ -webkit-appearance:none;
+ -moz-appearance:none
+
+}
+input[type="number"]::-webkit-input-placeholder,input[type="date"]::-webkit-input-placeholder,input[type="email"]::-webkit-input-placeholder,input[type="password"]::-webkit-input-placeholder,input[type="text"]:not(.input-addon-field)::-webkit-input-placeholder {
+ color:#222
+}
+input[type="number"]::-moz-placeholder,input[type="date"]::-moz-placeholder,input[type="email"]::-moz-placeholder,input[type="password"]::-moz-placeholder,input[type="text"]:not(.input-addon-field)::-moz-placeholder {
+ color:#222
+}
+input[type="number"]:-ms-input-placeholder,input[type="date"]:-ms-input-placeholder,input[type="email"]:-ms-input-placeholder,input[type="password"]:-ms-input-placeholder,input[type="text"]:not(.input-addon-field):-ms-input-placeholder {
+ color:#222
+}
+input[type="number"]:focus,input[type="date"]:focus,input[type="email"]:focus,input[type="password"]:focus,input[type="text"]:focus {
+ color:#ced4da;
+ border-color:rgba(82,168,236,0.8);
+ outline:0;
+ box-shadow:0 0 8px rgb(45, 60, 93)
+}
+input[type="number"] {
+ width:70px
+}
+input[type="text"]:not(.input-addon-field).form-numeric {
+ width:70px
+}
+input[type="text"]:not(.input-addon-field).form-datetime,input[type="text"]:not(.input-addon-field).form-date {
+ width:150px
+}
+input[type="text"]:not(.input-addon-field).form-input-large {
+ width:400px
+}
+input[type="text"]:not(.input-addon-field).form-input-small {
+ width:150px
+}
+textarea:focus {
+ color:#fff;
+ border-color:rgba(82,168,236,0.8);
+ outline:0;
+ box-shadow:0 0 8px rgba(82,168,236,0.6)
+}
+textarea {
+ padding:10px;
+ border:0px solid #ccc;
+ width:400px;
+ max-width:99%;
+ height:200px;
+ font-family:sans-serif;
+ font-size:1em;
+ line-height: 1.5;
+ color: #fff;
+ background-color: #252525;
+ border: 2px solid #4a5368;
+ border-radius: .25rem;
+ -webkit-transition: border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;
+ transition: border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;
+ transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
+ transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;
+}
+textarea::-webkit-input-placeholder {
+ color:#222
+}
+textarea::-moz-placeholder {
+ color:#222
+}
+textarea:-ms-input-placeholder {
+ color:#222
+}
+select{
+ max-width:95%;
+ border: 3px solid #4a5368;
+ background: #252525;
+ padding: 3px;
+ color:#eee;
+}
+select:focus {
+ outline:0
+}
+select[multiple] {
+ width:300px
+}
+.select2-container--default .select2-selection--multiple .select2-selection__choice {
+ background-color: #3b3e47;
+ border: 1px solid #3b3e47;
+ border-radius: 4px;
+ cursor: default;
+ float: left;
+ margin-right: 5px;
+ margin-top: 5px;
+ padding: 0 5px;
+}
+.tag-autocomplete {
+ width:400px
+}
+span.select2-container {
+ margin-top:2px
+}
+.form-actions {
+ padding-top:20px;
+ clear:both;
+}
+.form-required {
+ color:red;
+ padding-left:5px;
+ font-weight:bold
+}
+@media (max-width: 480px) {
+ .form-required {
+ display:none
+ }
+}
+input[type="text"].form-max-width {
+ width:100%
+}
+input.form-error,textarea.form-error {
+ border:2px solid #b94a48
+}
+input.form-error:focus,textarea.form-error:focus {
+ box-shadow:none;
+ border:2px solid #b94a48
+}
+.form-errors {
+ color:#b94a48;
+ list-style-type:none
+}
+ul.form-errors li {
+ margin-left:0
+}
+.form-help {
+ font-size: 0.9em;
+ color: #00c9ff;
+ margin-bottom:15px
+}
+.form-inline {
+ padding:0;
+ margin:0;
+ border:none
+}
+.form-inline label {
+ display:inline;
+ padding-right:3px
+}
+.form-inline input,.form-inline select {
+ margin:0 15px 0 0
+}
+.form-inline .form-required {
+ display:none
+}
+.form-inline .form-actions {
+ display:inline-block
+}
+.form-inline .js-submit-buttons-rendered {
+ display:inline-block
+}
+.form-inline-group {
+ display:inline
+}
+.form-columns {
+ display:-webkit-flex;
+ display:flex;
+ -webkit-flex-direction:row;
+ flex-direction:row;
+ -webkit-flex-wrap:wrap;
+ flex-wrap:wrap;
+ -webkit-justify-content:flex-start;
+ justify-content:flex-start
+}
+.form-columns .form-column {
+ margin-right:25px;
+ flex-grow:1
+}
+.form-columns fieldset {
+ margin-top:0
+}
+.form-login {
+ max-width:350px;
+ margin:5% auto 0
+}
+@media (max-width: 480px) {
+ .form-login {
+ margin-left:5px
+ }
+}
+.form-login li {
+ margin-left:25px;
+ line-height:25px
+}
+.form-login h2 {
+ margin-bottom:30px;
+ font-weight:bold
+}
+.reset-password {
+ margin-top:20px;
+ margin-bottom:20px
+}
+.reset-password a {
+ color:#999
+}
+.input-addon {
+ display:flex
+}
+.input-addon-field {
+ flex:1;
+ font-size:1em;
+ color:#ced4da;
+ background: #3e424d;
+ margin:0;
+ -webkit-appearance:none;
+ -moz-appearance:none
+}
+.input-addon-field:first-child {
+ border-radius:5px 0 0 5px
+}
+.input-addon-field:last-child {
+ border-radius:0 5px 5px 0
+}
+.input-addon-item {
+ background-color:rgba(147,128,108,0.1);
+ color:#666;
+ font:inherit;
+ font-weight:normal
+}
+.input-addon-item:first-child {
+ border-radius:5px 0 0 5px
+}
+.input-addon-item:last-child {
+ border-radius:0 5px 5px 0
+}
+@media (max-width: 480px) {
+ .input-addon-item .dropdown .fa-caret-down {
+ display:none
+ }
+}
+.input-addon-field,.input-addon-item {
+ border:0px solid rgba(147,128,108,0.25);
+ padding:4px 0.75em
+}
+.input-addon-field:not(:first-child),.input-addon-item:not(:first-child) {
+ border-left:0
+}
+.icon-success {
+ color:#468847
+}
+.icon-error {
+ color:#b94a48
+}
+.icon-fade-out {
+ opacity:1;
+ animation:icon-fadeout 5s linear forwards
+}
+@keyframes icon-fadeout {
+ 0% {
+ opacity:1
+ }
+ 100% {
+ opacity:0
+ }
+}
+.alert {
+ padding:8px 35px 8px 14px;
+ margin-top:5px;
+ margin-bottom:5px;
+ color:#fff;
+ background-color:#20c997;
+ border:0px solid #20c997;
+ border-radius:4px
+}
+.alert-success {
+ color: #ffffff;
+ background-color: #20c997;
+ border-color: #20c997;
+}
+.alert-error {
+ color:#b94a48;
+ background-color:#f2dede;
+ border-color:#eed3d7
+}
+.alert-info {
+ color:#3a87ad;
+ background-color:#d9edf7;
+ border-color:#bce8f1
+}
+.alert-normal {
+ color:#ced4da;
+ background-color:#f0f0f0;
+ border-color:#ddd
+}
+.alert ul {
+ margin-top:10px;
+ margin-bottom:10px
+}
+.alert li {
+ margin-left:25px
+}
+.alert-fade-out {
+ text-align:center;
+ position:fixed;
+ bottom:0;
+ left:20%;
+ width:60%;
+ padding-top:5px;
+ padding-bottom:5px;
+ margin-bottom:0;
+ border-width:1px 0 0;
+ border-radius:4px 4px 0 0;
+ z-index:9999;
+ opacity:1;
+ animation:fadeout 5s linear forwards
+}
+@keyframes fadeout {
+ 0% {
+ opacity:1
+ }
+ 100% {
+ opacity:0
+ }
+}
+a.btn {
+ text-decoration:none
+}
+.btn {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ font-size: 1.2em;
+ font-weight: normal;
+ cursor: pointer;
+ display: inline-block;
+ border-radius: 5px;
+ padding: 3px 10px;
+ margin: 0;
+ border: 0px solid #ddd;
+ background: #00bc8c;
+ color: #fbfbfb;
+}
+.btn:hover,.btn:focus {
+ border-color:#bbb;
+ background:#fafafa;
+ color:#000
+}
+.btn-red {
+ border-color:#b0281a;
+ background:#d14836;
+ color:#fff
+}
+.btn-red:hover,.btn-red:focus {
+ border-color:#b0281a;
+ background:#c53727;
+ color:#fff
+}
+.btn-blue {
+ border-color:#188ae2;
+ background:#188ae2;
+ color:#fff
+}
+.btn-blue:hover,.btn-blue:focus {
+ border-color:#1475bf;
+ background:#1475bf;
+ color:#fff
+}
+.btn:disabled {
+ color:#ccc;
+ border-color:#ccc;
+ background:#f7f7f7
+}
+.buttons-header {
+ font-size:0.8em;
+ margin-top:5px;
+ margin-bottom:15px
+}
+.tooltip i.fa {
+ cursor:pointer
+}
+.tooltip .fa-info-circle {
+ color:#999
+}
+#tooltip-container {
+ padding: 5px;
+ background: #3a404c;
+ border: 2px solid #ddd;
+ border-radius: 4px;
+ box-shadow: -1px -1px 7px #aaa;
+ position: absolute;
+ min-width: 350px;
+}
+#tooltip-container .markdown p {
+ margin-bottom:0
+}
+#tooltip-container .tooltip-large {
+ width:600px
+}
+h2 .dropdown ul {
+ display:none
+}
+.dropdown {
+ display:inline;
+ position:relative
+}
+.dropdown ul {
+ display:none
+}
+.dropdown-smaller {
+ font-size:0.85em
+}
+ul.dropdown-submenu-open {
+ display:block;
+ position:absolute;
+ z-index:1000;
+ min-width:285px;
+ list-style:none;
+ margin:3px 0 0 1px;
+ padding:6px 0;
+ background-color:#fff;
+ border:0px solid #b2b2b2;
+ border-radius:3px;
+ box-shadow:0 1px 3px rgba(0,0,0,0.15)
+}
+.dropdown-submenu-open li {
+ display:block;
+ margin:0;
+ padding:8px 10px;
+ font-size:0.9em;
+ border-bottom:0px solid #f8f8f8;
+ cursor:pointer
+}
+.dropdown-submenu-open li.no-hover {
+ cursor:default
+}
+.dropdown-submenu-open li:last-child {
+ border:none
+}
+.dropdown-submenu-open li:not(.no-hover):hover {
+ background:#222;
+ transition-duration: .05s;
+ color:#fff
+}
+.dropdown-submenu-open li:hover a {
+ color:#fff;
+ transition-duration: .05s;
+}
+.dropdown-submenu-open a {
+ text-decoration:none;
+ color:#3b3e47;
+ transition-duration: .05s;
+}
+.dropdown-submenu-open a:focus {
+ text-decoration:underline
+}
+.dropdown-menu-link-text,.dropdown-menu-link-icon {
+ color:#ced4da;
+ text-decoration:none
+}
+.dropdown-menu-link-text:hover {
+ text-decoration:underline
+}
+td a.dropdown-menu strong {
+ color:#ced4da
+}
+td a.dropdown-menu strong i {
+ color:#ced4da
+}
+td a.dropdown-menu i {
+ color:#ced4da
+}
+td a.dropdown-menu:hover strong {
+ color:#555
+}
+td a.dropdown-menu:hover strong i {
+ color:#555
+}
+td a.dropdown-menu:hover i {
+ color:#ced4da
+}
+.accordion-title {
+ background: #222;
+}
+.accordion-title h3 {
+ display:inline;
+ padding-right:5px;
+ background:#222;
+}
+.accordion-content {
+ margin-top:15px;
+ margin-bottom:25px
+}
+.accordion-toggle {
+ color:#ced4da;
+ text-decoration:none
+}
+.accordion-toggle:focus {
+ color:#ced4da
+}
+.accordion-toggle:hover {
+ color:#999
+}
+.accordion-toggle:before {
+ content:"\f0d7"
+}
+.accordion-collapsed {
+ margin-bottom:25px
+}
+.accordion-collapsed .accordion-toggle:before {
+ content:"\f0da"
+}
+.accordion-collapsed .accordion-content {
+ display:none
+}
+.select2-container--default .select2-selection--single {
+ background-color: #3b4658;
+ border: 1px solid #aaa;
+ border-radius: 4px;
+}
+.select2-container--default .select2-selection--single .select2-selection__rendered {
+ color: #fff;
+ line-height: 28px;
+}
+.select2-container--default .select2-results__option--highlighted[aria-selected] {
+ background-color: #3b4658;
+ color: white;
+}
+.select2-container--default .select2-results>.select2-results__options {
+ max-height: 200px;
+ overflow-y: auto;
+ background-color: #3b4658;
+}
+.select2-container--default .select2-results__option[aria-selected="true"] {
+ background-color: #4e5663!important;
+ color: #fff!important;
+}
+#select-dropdown-menu {
+ position:absolute;
+ display:block;
+ z-index:1000;
+ min-width:160px;
+ padding:5px 0;
+ background:#3b4658;
+ list-style:none;
+ border:0px solid #ccc;
+ border-radius:3px;
+ box-shadow:0 6px 12px rgba(0,0,0,0.175);
+ overflow-x: hidden;
+}
+.select-dropdown-menu-item {
+ white-space:nowrap;
+ overflow:hidden;
+ padding:3px 10px;
+ color:#ced4da;
+ cursor:pointer;
+ border-bottom:0px solid #f8f8f8;
+ line-height:1.5em;
+ font-weight:400
+}
+.select-dropdown-menu-item.active {
+ color:#fff;
+ background:#428bca
+}
+.select-dropdown-menu-item:last-child {
+ border:none
+}
+.select-dropdown-input-container {
+ position:relative;
+ border:0px solid #ccc;
+ border-radius:5px;
+ background-color:#fff
+}
+.select-dropdown-input-container input.select-dropdown-input {
+ margin:0 0 0 5px;
+ border:none;
+ height:23px
+}
+.select-dropdown-input-container input.select-dropdown-input:focus {
+ border:none;
+ box-shadow:none
+}
+.select-dropdown-input-container .select-dropdown-chevron {
+ color:#555;
+ position:absolute;
+ top:4px;
+ right:5px;
+ cursor:pointer
+}
+.select-dropdown-input-container .select-loading-icon {
+ color:#555;
+ position:absolute;
+ top:4px;
+ right:5px;
+}
+#suggest-menu {
+ position:absolute;
+ display:block;
+ z-index:1000;
+ min-width:160px;
+ padding:5px 0;
+ background:#fff;
+ list-style:none;
+ border:0px solid #ccc;
+ border-radius:3px;
+ box-shadow:0 6px 12px rgba(0,0,0,0.175)
+}
+.suggest-menu-item {
+ white-space:nowrap;
+ padding:3px 10px;
+ color:#ced4da;
+ font-weight:bold;
+ cursor:pointer
+}
+.suggest-menu-item.active {
+ color:#fff;
+ background:#428bca
+}
+.suggest-menu-item.active small {
+ color:#fff
+}
+.suggest-menu-item small {
+ color:#999;
+ font-weight:normal
+}
+#modal-overlay {
+ position:fixed;
+ top:0;
+ left:0;
+ width:100%;
+ height:100%;
+ background:rgba(0,0,0,0.9);
+ overflow:auto;
+ z-index:100
+}
+#modal-box {
+ position:fixed;
+ max-height:calc(100% - 30px);
+ top:0%;
+ padding: 20px;
+ left:50%;
+ transform:translateX(-50%);
+ background:#252525;
+ overflow:auto;
+ border-radius:5px
+}
+#modal-content {
+ padding:0 5px 5px
+}
+#modal-header {
+ text-align:right;
+ padding-right:5px
+}
+#modal-close-button {
+ color:#ced4da
+}
+#modal-close-button:hover {
+ color:#b94a48
+}
+.pagination {
+ text-align:center;
+ font-size:0.9em
+}
+.pagination-showing {
+ margin-right:5px;
+ padding-right:5px;
+ border-right:0px solid #999
+}
+.pagination-next {
+ margin-left:5px
+}
+.pagination-previous {
+ margin-right:5px
+}
+header {
+ border-bottom: none;
+ box-shadow: 0px 1px 3px 0 rgba(46,61,73,.12);
+ padding: 15px 10px;
+ margin-bottom: 15px;
+ background: #303030;
+}
+.header img {
+ float: left;
+}
+.header h2 {
+ position: relative;
+ color:#ced4da;
+ top: 16px;
+ left: 10px;
+ margin: 0;
+}
+header .title-container {
+ flex:1;
+ min-width:300px
+}
+@media (max-width: 480px) {
+ header .title-container {
+ order:3
+ }
+}
+header .board-selector-container {
+ min-width:320px;
+ display:flex;
+ align-items:center
+}
+@media (max-width: 480px) {
+ header .board-selector-container {
+ order:2;
+ min-width:300px
+ }
+ header .board-selector-container input[type=text] {
+ max-width:280px
+ }
+}
+header .menus-container {
+ min-width:120px;
+ display:flex;
+ align-items:center;
+ justify-content:flex-end
+}
+@media (max-width: 480px) {
+ header .menus-container {
+ order:1;
+ margin-bottom:5px;
+ margin-left:auto
+ }
+}
+header h1 {
+ font-size:1.33em!important;
+}
+header h1 .tooltip {
+ opacity:0.3;
+ font-size:0.7em
+}
+a i.web-notification-icon {
+ color:#00bc8c
+}
+a i.web-notification-icon:focus,a i.web-notification-icon:hover {
+ color:#000
+}
+.logo a {
+ opacity:0.5;
+ color:#d40000;
+ text-decoration:none
+}
+.logo span {
+ color:#ced4da
+}
+.logo a:hover {
+ opacity:0.8;
+ color:#ced4da
+}
+.logo a:focus span,.logo a:hover span {
+ color:#d40000
+}
+.page-header {
+ margin-bottom:20px
+}
+.page-header .dropdown {
+ padding-right:10px
+}
+.page-header h2 {
+ margin:0;
+ padding:0;
+ font-weight:bold;
+ border-bottom:0px dotted #ccc
+}
+.page-header h2 a {
+ color:#ced4da;
+ text-decoration:none
+}
+.page-header h2 a:focus,.page-header h2 a:hover {
+ color:#999
+}
+.page-header ul {
+ text-align:left;
+ margin-top:5px;
+ display:inline-block
+}
+.page-header li {
+ display:inline;
+ padding-right:15px
+}
+@media (max-width: 480px) {
+ .page-header li {
+ display:block;
+ line-height:1.5em
+ }
+}
+.page-header li.active a {
+ color:#ced4da;
+ text-decoration:none;
+ font-weight:bold
+}
+.page-header li.active a:hover,.page-header li.active a:focus {
+ text-decoration:underline
+}
+.menu-inline {
+ margin-bottom:5px
+}
+.menu-inline li {
+ display:inline;
+ padding-right:15px
+}
+.menu-inline li .active a {
+ font-weight:bold;
+ color:#000;
+ text-decoration:none
+}
+.sidebar-container {
+ height:100%;
+ display:flex;
+ flex-flow:row
+}
+@media (max-width: 768px) {
+ .sidebar-container {
+ flex-flow:wrap
+ }
+}
+.sidebar-content {
+ padding-left:10px;
+ flex:1 100%;
+ max-width:85%;
+ overflow-wrap:break-word
+}
+@media (max-width: 768px) {
+ .sidebar-content {
+ padding-left:0;
+ order:1;
+ max-width:100%
+ }
+}
+@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: landscape) and (-webkit-min-device-pixel-ratio: 1) {
+ .sidebar-content {
+ max-width:75%
+ }
+}
+.sidebar {
+ max-width:25%;
+ min-width:230px;
+ background-color: #252525;
+ padding: 20px;
+}
+@media (max-width: 768px) {
+ .sidebar {
+ flex:1 auto;
+ order:2
+ }
+}
+.sidebar h2 {
+ margin-top:0
+}
+.sidebar>ul a {
+ text-decoration:none;
+ color:#ced4da;
+ font-weight:300
+}
+.sidebar>ul a:hover {
+ color:#ced4da
+}
+.sidebar>ul li {
+ list-style-type:none;
+ line-height:35px;
+ border-bottom:0px dotted #efefef;
+ padding-left:13px
+}
+.sidebar>ul li:hover {
+ border-left:5px solid #00bc8c;
+ padding-left:8px
+}
+.sidebar>ul li.active {
+ border-left:5px solid #00bc8c;
+ padding-left:8px
+}
+.sidebar>ul li.active a {
+ color:#ced4da;
+ font-weight:bold
+}
+.sidebar-icons>ul li {
+ padding-left:10px
+}
+.sidebar-icons>ul li:hover,.sidebar-icons>ul li.active {
+ padding-left: 10px;
+ border-left: none;
+ background-color: #3b404b;
+}
+.sidebar>ul li.active a:focus,.sidebar>ul li.active a:hover {
+ color:#ced4da
+}
+.sidebar>ul li:last-child {
+ margin-bottom:15px
+}
+.avatar img {
+ vertical-align:bottom
+}
+.avatar-left {
+ float:left;
+ margin-right:10px
+}
+.avatar-inline {
+ display:inline-block;
+ margin-right:3px
+}
+.avatar-48 img,.avatar-48 div {
+ border-radius:30px
+}
+.avatar-48 .avatar-letter {
+ line-height:48px;
+ width:48px;
+ font-size:25px
+}
+.avatar-20 img,.avatar-20 div {
+ border-radius:10px
+}
+.avatar-20 .avatar-letter {
+ line-height:20px;
+ width:20px;
+ font-size:11px
+}
+.avatar-letter {
+ color:#fff;
+ text-align:center
+}
+#file-dropzone,#screenshot-zone {
+ position:relative;
+ border:2px dashed #ccc;
+ width:99%;
+ height:250px;
+ overflow:auto
+}
+#file-dropzone-inner,#screenshot-inner {
+ position:absolute;
+ left:0;
+ bottom:48%;
+ width:100%;
+ text-align:center;
+ color:#aaa
+}
+#screenshot-zone.screenshot-pasted {
+ border:2px solid #ced4da
+}
+#file-list {
+ margin:20px
+}
+#file-list li {
+ list-style-type:none;
+ padding-top:8px;
+ padding-bottom:8px;
+ border-bottom:0px dotted #ddd;
+ width:95%
+}
+#file-list li .file-error {
+ font-weight:bold;
+ color:#b94a48
+}
+.file-thumbnails {
+ display:-webkit-flex;
+ display:flex;
+ -webkit-flex-direction:row;
+ flex-direction:row;
+ -webkit-flex-wrap:wrap;
+ flex-wrap:wrap;
+ -webkit-justify-content:flex-start;
+ justify-content:flex-start
+}
+.file-thumbnail {
+ width:250px;
+ border:0px solid #efefef;
+ border-radius:5px;
+ margin-bottom:20px;
+ box-shadow:4px 2px 10px -6px rgba(0,0,0,0.55);
+ margin-right:15px
+}
+.file-thumbnail img {
+ cursor:pointer;
+ border-top-left-radius:5px;
+ border-top-right-radius:5px
+}
+.file-thumbnail img:hover {
+ opacity:0.5
+}
+.file-thumbnail-content {
+ padding-left:8px;
+ padding-right:8px
+}
+.file-thumbnail-title {
+ font-weight:700;
+ font-size:0.9em;
+ color:#555;
+ overflow:hidden;
+ text-overflow:ellipsis
+}
+.file-thumbnail-description {
+ font-size:0.8em;
+ color:#999;
+ margin-top:8px;
+ margin-bottom:5px
+}
+.file-viewer {
+ position:relative
+}
+.file-viewer img {
+ max-width:95%;
+ max-height:85%;
+ margin-top:10px
+}
+.color-picker {
+ width:180px
+}
+.color-picker-option {
+ height:25px
+}
+.color-picker-square {
+ display:inline-block;
+ width:18px;
+ height:18px;
+ margin-right:5px;
+ border:0px solid #000
+}
+.color-picker-label {
+ display:inline-block;
+ vertical-align:bottom;
+ padding-bottom:3px
+}
+.filter-box {
+ max-width:1024px
+}
+.action-menu {
+ color:#ced4da;
+ text-decoration:none
+}
+.action-menu:hover,.action-menu:focus {
+ text-decoration:underline
+}
+.js-project-creation-options {
+ max-width:500px;
+ border-left:1px solid #6e727b;
+ margin-top:20px;
+ padding-left:15px;
+ padding-bottom:5px;
+ padding-top:5px
+}
+.project-overview-columns {
+ display:-webkit-flex;
+ display:flex;
+ -webkit-flex-direction:row;
+ flex-direction:row;
+ -webkit-flex-wrap:wrap;
+ flex-wrap:wrap;
+ -webkit-align-items:center;
+ align-items:center;
+ -webkit-justify-content:center;
+ justify-content:center;
+ margin-bottom:20px;
+ font-size:1.4em
+}
+@media (max-width: 480px) {
+ .project-overview-columns {
+ display:block
+ }
+}
+.project-overview-column {
+ text-align:center;
+ margin-right:3%;
+ margin-top:5px;
+ padding:3px 15px 3px 15px;
+ border:1px dashed #999
+}
+@media (max-width: 480px) {
+ .project-overview-column {
+ text-align:left
+ }
+}
+.project-overview-column small {
+ color:#e5e5e5
+}
+.project-overview-column strong {
+ color:#eee;
+ display:block
+}
+@media (max-width: 480px) {
+ .project-overview-column strong {
+ display:inline
+ }
+}
+.project-header {
+ margin-bottom:8px
+}
+.project-header .dropdown-component {
+ margin-top:4px;
+ margin-right:5px;
+ float:left
+}
+@media (max-width: 768px) {
+ .project-header .dropdown-component {
+ float:none
+ }
+}
+.project-header .views-switcher-component {
+ margin-top:4px;
+ float:left
+}
+@media (max-width: 768px) {
+ .project-header .views-switcher-component {
+ float:none;
+ margin-bottom:10px
+ }
+}
+.project-header .filter-box-component form {
+ margin:0
+}
+.views {
+ margin-right:10px;
+ margin-top:1px;
+ font-size:0.9em
+}
+@media (max-width: 560px) {
+ .views {
+ width:100%
+ }
+}
+@media (max-width: 768px) {
+ .views {
+ margin-top:10px;
+ font-size:1em
+ }
+}
+@media (max-width: 480px) {
+ .views {
+ margin-top:5px
+ }
+}
+.views li {
+ white-space:nowrap;
+ background:rgba(147,128,108,0.1);
+ border:0px solid #ddd;
+ border-right:none;
+ padding:4px 8px;
+ display:inline
+}
+@media (max-width: 560px) {
+ .views li {
+ display:block;
+ margin-top:5px;
+ border-radius:5px;
+ border:0px solid #ddd
+ }
+}
+.views li.active a {
+ font-weight:bold;
+ color:#00bc8c;
+ text-decoration:none
+}
+.views li:first-child {
+ border-top-left-radius:5px;
+ border-bottom-left-radius:5px
+}
+.views li:last-child {
+ border-right:0px solid #ddd;
+ border-top-right-radius:5px;
+ border-bottom-right-radius:5px
+}
+.views a {
+ color:#ced4da;
+ text-decoration:none
+}
+.views a:hover {
+ color:#fff;
+ text-decoration:none;
+}
+.dashboard-project-stats small {
+ margin-right:10px;
+ color:#999
+}
+.dashboard-table-link {
+ font-weight:bold;
+ color:#000;
+ text-decoration:none
+}
+.dashboard-table-link:focus,.dashboard-table-link:hover {
+ color:#999
+}
+.public-board {
+ margin-top:5px
+}
+.public-task {
+ max-width:800px;
+ margin:5px auto 0
+}
+#board-container {
+ overflow-x:auto
+}
+#board {
+ table-layout:fixed;
+ margin-bottom:0
+}
+#board th.board-column-header {
+ width:240px
+}
+#board td {
+ vertical-align:top
+}
+.board-container-compact {
+ overflow-x:initial
+}
+@media all and (-ms-high-contrast: active), (-ms-high-contrast: none) {
+ .board-container-compact #board {
+ table-layout:auto
+ }
+}
+#board th.board-column-header.board-column-compact {
+ width:initial
+}
+.board-column-collapsed {
+ display:none
+}
+td.board-column-task-collapsed {
+ font-weight:bold;
+ background-color:#222
+}
+#board th.board-column-header-collapsed {
+ width:28px;
+ min-width:28px;
+ text-align:center;
+ overflow:hidden
+}
+.board-rotation-wrapper {
+ position:relative;
+ padding:8px 4px;
+ min-height:150px;
+ overflow:hidden
+}
+.board-rotation {
+ white-space:nowrap;
+ -webkit-backface-visibility:hidden;
+ -webkit-transform:rotate(90deg);
+ -moz-transform:rotate(90deg);
+ -ms-transform:rotate(90deg);
+ transform:rotate(90deg);
+ -webkit-transform-origin:0 100%;
+ -moz-transform-origin:0 100%;
+ -ms-transform-origin:0 100%;
+ transform-origin:0 100%
+}
+.board-column-title .dropdown-menu {
+ text-decoration:none
+}
+.board-add-icon {
+ float:left;
+ padding:0 5px
+}
+.board-add-icon i {
+ text-decoration:none;
+ color:#00bc8c;
+ font-size:1.4em
+}
+.board-add-icon i:focus,.board-add-icon i:hover {
+ text-decoration:none;
+ color:#20c997
+}
+.board-column-header-task-count {
+ color:#999;
+ font-weight:normal
+}
+a.board-swimlane-toggle {
+ text-decoration:none
+}
+a.board-swimlane-toggle:hover,a.board-swimlane-toggle:focus {
+ color:#000;
+ text-decoration:none;
+ border:none
+}
+.board-task-list {
+ min-height:60px
+}
+.board-task-list .task-board:last-child {
+ margin-bottom:0
+}
+.board-task-list-limit {
+ background-color:#DF5353
+}
+.draggable-item {
+ cursor:pointer;
+ user-select:none;
+ -webkit-user-select:none;
+ -moz-user-select:none
+}
+.draggable-placeholder {
+ border:2px dashed #000;
+ background:#fafafa;
+ height:70px;
+ margin-bottom:10px
+}
+div.draggable-item-selected {
+ border:0px solid #000
+}
+.task-board-sort-handle {
+ float:left;
+ padding-right:5px
+}
+.task-board {
+ position:relative;
+ margin-bottom:4px;
+ border:2px solid;
+ padding:10px;
+ word-wrap:break-word;
+ font-size:0.9em;
+ border-radius:6px;
+ background-color:#252525!important;
+}
+div.task-board-recent {
+ border-width:2px
+}
+div.task-board-status-closed {
+ user-select:none;
+ border:0px dotted #555
+}
+.task-board a {
+ color:#00bc8c;
+ text-decoration:none
+}
+.task-board-collapsed {
+ overflow:hidden;
+ white-space:nowrap;
+ text-overflow:ellipsis
+}
+.task-board-title {
+ margin-top:5px;
+ margin-bottom:8px
+}
+.task-board-title a:hover {
+ text-decoration:underline
+}
+.task-board-saving-state {
+ opacity:0.3
+}
+.task-board-saving-icon {
+ position:absolute;
+ margin:auto;
+ width:100%;
+ text-align:center;
+ color:#000
+}
+.task-board-avatars {
+ text-align:right;
+ float:right
+}
+.task-board-change-assignee {
+ cursor:pointer
+}
+.task-board-change-assignee:hover {
+ opacity:0.6
+}
+.task-list-avatars {
+ display:inline-block;
+ float:left
+}
+.c3-chart-arc path {
+ stroke: #fff0;
+}
+@media (max-width: 768px) {
+ .task-list-avatars {
+ float:none;
+ display:block
+ }
+}
+.task-list-avatars .task-avatar-assignee {
+ font-weight:300;
+ color:#999
+}
+.task-list-avatars:hover .task-avatar-assignee {
+ font-weight:400;
+ color:#000
+}
+.task-board-icons,.task-list-icons {
+ font-size:0.8em;
+ text-align:right
+}
+.task-board-icons a,.task-list-icons a {
+ text-decoration:none
+}
+.task-board-icons a:hover,.task-list-icons a:hover {
+ color:#ced4da
+}
+.task-board-icons a:hover i,.task-list-icons a:hover i {
+ color:#ced4da
+}
+.task-board-icons .task-score,.task-list-icons .task-score {
+ font-weight:bold
+}
+.task-board-icons .flag-milestone,.task-list-icons .flag-milestone {
+ color:green
+}
+.task-board-icons {
+ margin-top:7px
+}
+.task-board-icons a {
+ opacity:0.5;
+}
+.task-board-icons span {
+ opacity:0.5;
+ margin-left:4px;
+ color:#97d2ff;
+}
+.task-board-icons a:hover {
+ opacity:1.0;
+ font-weight:bold
+}
+.task-board-icons .task-board-icons-row {
+ line-height:22px
+}
+.task-list-icons {
+ line-height:22px
+}
+.task-list-icons a,.task-list-icons span,.task-list-icons i {
+ color:#999;
+ opacity:1.0
+}
+.task-list-icons span {
+ margin-left:5px
+}
+@media (max-width: 768px) {
+ .task-list-icons {
+ text-align:left
+ }
+}
+.task-icon-age {
+ display:inline-block
+}
+span.task-icon-age-total {
+ border:0px solid #e5e5e5;
+ padding:1px 3px 1px 3px;
+ border-top-left-radius:3px;
+ border-bottom-left-radius:3px
+}
+span.task-icon-age-column {
+ border:0px solid #e5e5e5;
+ border-left:none;
+ margin-left:-5px;
+ padding:1px 3px 1px 3px;
+ border-top-right-radius:3px;
+ border-bottom-right-radius:3px
+}
+.task-board span.task-icon-age-total,.task-board span.task-icon-age-column {
+ border-color:#666
+}
+.task-board-category-container {
+ text-align:right;
+ margin-top:8px;
+ margin-bottom:8px
+}
+.task-board-category {
+ border:0px solid #555;
+ font-size:0.9em;
+ font-weight:500;
+ color:#000;
+ padding:1px 3px 1px 2px;
+ border-radius:3px
+}
+.task-board-category a:hover {
+ text-decoration:underline
+}
+.task-date {
+ font-weight:500;
+ color:#000
+}
+span.task-date-today {
+ opacity:1.0;
+ color:#00bc8c
+}
+span.task-date-overdue {
+ opacity:1.0;
+ color:#b94a48
+}
+.task-tags li {
+ display:inline-block;
+ margin:3px 3px 0 0;
+ padding:1px 3px 1px 3px;
+ border:0px solid #ced4da;
+ border-radius:4px;
+ background:#20c997;
+}
+.select2-container--default .select2-results__option[aria-selected="true"] {
+ background-color: #e5e5e5;
+ color: #333;
+}
+.task-summary-container .task-tags {
+ margin-top:10px
+}
+.task-list-tag {
+ background:#20c997;
+ border-color:#20c997;
+ padding-left: 3px;
+}
+#task-summary {
+ margin-bottom:15px
+}
+#task-summary h2 {
+ color:#f7f7f7;
+ font-size:1.6em;
+ margin-top:0;
+ padding-top:0
+}
+.task-summary-container {
+ border: 2px solid;
+ border-radius: 8px;
+ padding: 10px;
+ background-color:#29303e!important;
+}
+.task-summary-columns {
+ display:flex;
+ flex-flow:row;
+ justify-content:space-between
+}
+@media (max-width: 768px) {
+ .task-summary-columns {
+ flex-flow:column
+ }
+}
+.task-summary-column {
+ color:#ced4da
+}
+.task-summary-column span {
+ color:#ced4cb
+}
+.task-summary-column li {
+ line-height:23px
+}
+#external-task-view {
+ padding:10px;
+ margin-top:10px;
+ margin-bottom:10px;
+ border:0px dotted #ccc
+}
+.task-form-container {
+ box-sizing:border-box;
+ display:flex;
+ flex-wrap:wrap
+}
+.task-form-container>* {
+ box-sizing:border-box
+}
+.task-form-container>* {
+ width:1%
+}
+.task-form-main-column {
+ width:60%
+}
+@media (max-width: 1000px) {
+ .task-form-main-column {
+ width:100%
+ }
+}
+.task-form-main-column input[type="text"] {
+ width:700px;
+ max-width:99%
+}
+.task-form-secondary-column {
+ max-width:250px;
+ min-width:200px;
+ max-height:600px;
+ padding-left:10px;
+ overflow:auto;
+ width:20%
+}
+@media (max-width: 1000px) {
+ .task-form-secondary-column {
+ width:100%;
+ max-width:99%;
+ max-height:none
+ }
+}
+@media (max-width: 768px) {
+ .task-form-secondary-column {
+ padding-left:0
+ }
+}
+.task-form-secondary-column label:first-child {
+ margin-top:0
+}
+@media (max-width: 1000px) {
+ .task-form-secondary-column label:first-child {
+ margin-top:10px
+ }
+}
+.task-form-bottom {
+ width:100%
+}
+.comment-sorting {
+ text-align:right
+}
+.comment-sorting a {
+ color:#f7f7f7;
+ font-weight:normal;
+ text-decoration:none
+}
+.comment-sorting a:hover {
+ color:#999
+}
+.comment {
+ padding:5px;
+ margin-bottom:15px
+}
+.comment-title {
+ border-bottom:0px dotted #eee;
+ margin-left:55px
+}
+.comment-date {
+ color:#999;
+ font-weight:200
+}
+.comment-actions {
+ text-align:right
+}
+.comment-content {
+ margin-left:55px
+}
+.comments .text-editor textarea {
+ height:90px
+}
+.comments .text-editor .text-editor-preview-area {
+ height:90px
+}
+.comments .comment-highlighted {
+ background-color:#252525;
+ border:2px solid #ffeb8e
+}
+.comments .comment-highlighted:hover {
+ background-color:#252525
+}
+.comments .comment:hover {
+ background:#252525
+}
+.comments .comment:nth-child(even):not(.comment-highlighted) {
+ background:#222
+}
+.comments .comment:nth-child(even):not(.comment-highlighted):hover {
+ background:#252525
+}
+.subtask-cell {
+ padding:4px 10px;
+ border-top:0px dotted #222;
+ border-left:0px dotted #222;
+ display:table-cell;
+ vertical-align:middle;
+}
+.subtask-cell a {
+ color:#ced4da;
+ text-decoration:none
+}
+.subtask-cell a:hover,.subtask-cell a:focus {
+ color:#00bc8c;
+}
+.subtask-cell:first-child {
+ border-left:none
+}
+@media (max-width: 768px) {
+ .subtask-cell {
+ width:90%;
+ display:block;
+ border-left:none
+ }
+}
+.task-list-subtasks {
+ display:table;
+ width:100%
+}
+@media (max-width: 768px) {
+ .task-list-subtasks {
+ display:block
+ }
+}
+.task-list-subtask {
+ display:table-row
+}
+@media (max-width: 768px) {
+ .task-list-subtask {
+ display:block
+ }
+}
+@media (max-width: 768px) {
+ .subtask-assignee,.subtask-time-tracking-cell {
+ display:none
+ }
+}
+.task-links-table td {
+ vertical-align:middle
+}
+.task-links-task-count {
+ color:#999;
+ font-weight:normal
+}
+.task-link-closed {
+ text-decoration:line-through
+}
+.text-editor {
+ margin-top:10px
+}
+.text-editor-toolbar {
+ margin-bottom: 10px;
+}
+.text-editor a {
+ font-size:1em;
+ color:#999;
+ text-decoration:none;
+ margin-right:10px
+}
+.text-editor a:hover {
+ color:#00bc8c
+}
+.text-editor .text-editor-preview-area {
+ border:0px solid #222;
+ width:700px;
+ max-width:99%;
+ height:250px;
+ overflow:auto;
+ padding:2px
+}
+.text-editor textarea {
+ width:700px;
+ max-width:98%;
+ height:250px
+}
+.markdown {
+ line-height:1.4em
+}
+.markdown h1 {
+ margin-top:5px;
+ margin-bottom:10px;
+ font-weight:bold
+}
+.markdown h2 {
+ font-weight:bold
+}
+.markdown p {
+ margin-bottom:10px
+}
+.markdown ol,.markdown ul {
+ margin-left:25px;
+ margin-top:10px;
+ margin-bottom:10px
+}
+.markdown pre {
+ background:#242729;
+ padding:10px;
+ border-radius:5px;
+ border:0px solid #ddd;
+ overflow:auto;
+ overflow-wrap:initial;
+ color:#555
+}
+.markdown blockquote {
+ font-style:italic;
+ border-left:3px solid #ddd;
+ padding-left:10px;
+ margin-bottom:10px;
+ margin-left:20px
+}
+.markdown img {
+ display:block;
+ max-width:80%;
+ margin-top:10px
+}
+.documentation {
+ margin:0 auto;
+ padding:20px;
+ max-width:850px;
+ background:#fefefe;
+ border:0px solid #ccc;
+ border-radius:5px;
+ color:#555
+}
+.documentation img {
+ border:0px solid #ced4da
+}
+.documentation h1 {
+ text-decoration:none;
+ margin-bottom:30px
+}
+.documentation h2 {
+ text-decoration:none;
+ border-bottom:0px solid #ccc;
+ margin-bottom:25px
+}
+.documentation li {
+ line-height:30px
+}
+.panel {
+ border-radius:4px;
+ padding:8px 35px 8px 10px;
+ margin-top:10px;
+ margin-bottom:15px;
+ border:0px solid #ddd;
+ color:#ced4da;
+ background-color:#252525;
+ overflow:auto
+}
+.panel li {
+ list-style-type:square;
+ margin-left:20px;
+ line-height:1.35em
+}
+.activity-event {
+ margin-bottom:15px;
+ padding:10px
+}
+.activity-event:nth-child(even) {
+ background:#252525
+}
+.activity-event:hover {
+ background:#252525
+}
+.activity-date {
+ margin-left:10px;
+ font-weight:normal;
+ color:#999
+}
+.activity-content {
+ margin-left:55px
+}
+.activity-title {
+ font-weight:bold;
+ color:#ced4da;
+ border-bottom:0px dotted #efefef
+}
+.activity-description {
+ color:#f7f7f7;
+ margin-top:10px
+}
+@media (max-width: 480px) {
+ .activity-description {
+ overflow:auto
+ }
+}
+.activity-description li {
+ list-style-type:circle
+}
+.activity-description ul {
+ margin-top:10px;
+ margin-left:20px
+}
+.user-mention-link {
+ font-weight:bold;
+ color:#5897fb;
+ text-decoration:none
+}
+.user-mention-link:hover {
+ color:#fff
+}
+.image-slideshow-overlay {
+ position:fixed;
+ top:0;
+ left:0;
+ width:100%;
+ height:100%;
+ background:rgba(0,0,0,0.95);
+ overflow:auto;
+ z-index:100
+}
+.image-slideshow-overlay img {
+ display:block;
+ margin:auto;
+ max-width: 100%;
+}
+.image-slideshow-overlay figcaption {
+ color:#fff;
+ opacity:0.7;
+ position:absolute;
+ bottom:5px;
+ right:15px
+}
+.slideshow-icon {
+ color:#fff;
+ position:absolute;
+ font-size:2.5em;
+ opacity:0.6
+}
+.slideshow-icon:hover {
+ opacity:0.9;
+ cursor:pointer
+}
+.slideshow-previous-icon {
+ left:10px;
+ top:45%
+}
+.slideshow-next-icon {
+ right:10px;
+ top:45%
+}
+.slideshow-close-icon {
+ right:10px;
+ top:10px;
+ font-size:1.4em
+}
+.slideshow-download-icon {
+ left:10px;
+ bottom:10px;
+ font-size:1.3em
+}
+.list-item-links,.list-item-actions {
+ display:inline-block;
+ float:left;
+ margin-left:10px
+}
+.list-item-links a {
+ margin:0
+}
+.list-item-action-hidden {
+ display:none
+}
+.bulk-change-checkbox {
+ float:left
+}
+.bulk-change-inputs {
+ float:left;
+ padding-left:10px
+}
+.bulk-change-inputs label {
+ margin-top:0;
+ margin-bottom:3px
+}
+/* style plugin-gantt */
+div.ganttview-hzheader-month, div.ganttview-hzheader-day, div.ganttview-vtheader, div.ganttview-vtheader-item-name, div.ganttview-vtheader-series, div.ganttview-grid, div.ganttview-grid-row-cell {
+ float: left;
+}
+
+div.ganttview-hzheader-month, div.ganttview-hzheader-day {
+ text-align: center;
+}
+
+div.ganttview-grid-row-cell.last, div.ganttview-hzheader-day.last, div.ganttview-hzheader-month.last {
+ border-right: none;
+}
+
+div.ganttview {
+ border: 1px solid #999;
+}
+
+div.ganttview-hzheader-month {
+ width: 60px;
+ height: 20px;
+ border-right: 1px solid #d0d0d0;
+ line-height: 20px;
+ overflow: hidden;
+}
+
+div.ganttview-hzheader-day {
+ width: 20px;
+ height: 20px;
+ border-right: 1px solid #f0f0f0;
+ border-top: 1px solid #d0d0d0;
+ line-height: 20px;
+ color: #f5f5f5!important;
+}
+
+div.ganttview-vtheader {
+ margin-top: 41px;
+ width: 400px;
+ overflow: hidden;
+ background-color: #fff;
+}
+
+div.ganttview-vtheader-item {
+ color: #555;
+}
+
+div.ganttview-vtheader-series-name {
+ width: 400px;
+ height: 31px;
+ line-height: 31px;
+ padding-left: 3px;
+ color: #eee!important;
+ background: #2f3948!important;
+ border-top: 1px solid #d0d0d0;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+div.ganttview-vtheader-series-name a {
+ color: #f5f5f5!important;
+ text-decoration: none;
+}
+
+div.ganttview-vtheader-series-name a:hover {
+ color: #333;
+ text-decoration: underline;
+}
+
+div.ganttview-vtheader-series-name a i {
+ color: #f5f5f5!important;
+}
+
+div.ganttview-vtheader-series-name a:hover i {
+ color: #eee!important;
+}
+
+div.ganttview-slide-container {
+ overflow: auto;
+ border-left: 1px solid #999;
+}
+
+div.ganttview-grid-row-cell {
+ width: 20px;
+ height: 31px;
+ border-right: 1px solid #f0f0f0;
+ border-top: 1px solid #f0f0f0;
+}
+
+div.ganttview-grid-row-cell.ganttview-weekend {
+ background-color: #2f3948!important;
+}
+
+div.ganttview-grid-row-cell.ganttview-today {
+ background-color: #0979b6!important;
+}
+
+div.ganttview-blocks {
+ margin-top: 40px;
+}
+
+div.ganttview-block-container {
+ height: 28px;
+ padding-top: 4px;
+}
+
+div.ganttview-block {
+ position: relative;
+ height: 25px;
+ background-color: #E5ECF9;
+ border: 1px solid #c0c0c0;
+ border-radius: 3px;
+}
+
+.ganttview-block-movable {
+ cursor: move;
+}
+
+div.ganttview-block-text {
+ position: absolute;
+ height: 12px;
+ font-size: 0.7em;
+ color: #666;
+ padding: 2px 3px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+div.ganttview-block div.ui-resizable-handle.ui-resizable-s {
+ bottom: 0;
+}
+
+div.ganttview-progress-bar {
+ z-index: 0;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ opacity: 0.4;
+}
diff --git a/plugins/Customizer/Assets/css/themes/Github.css b/plugins/Customizer/Assets/css/themes/Github.css
new file mode 100644
index 00000000..5fe5a3d3
--- /dev/null
+++ b/plugins/Customizer/Assets/css/themes/Github.css
@@ -0,0 +1,3260 @@
+/*!
+ * Derived from Moon - Theme for Kanboard by kenlog
+ * Licensed under the MIT license - Moon/LICENSE
+ * https://github.com/kenlog/Moon
+ * Copyright (c) 2018 Valentino Pesce - https://iltuobrand.it
+ */
+
+h1,
+li,
+ul,
+ol,
+table,
+tr,
+td,
+th,
+p,
+blockquote,
+body {
+ margin: 0;
+ padding: 0;
+ font-size: 100%
+}
+
+body {
+ padding-bottom: 10px;
+ color: #8d9498;
+ background-color: #fefefe;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ text-rendering: optimizeLegibility
+}
+
+::selection{background:rgba(113,113,113,0.5);color:#ffffff}::-moz-selection{background:rgba(113,113,113,0.5);color:#ffffff}
+
+small {
+ font-size: 0.8em
+}
+
+hr {
+ border: 0;
+ height: 0;
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
+ border-bottom: 1px solid rgba(255, 255, 255, 0.3)
+}
+
+.page {
+ margin-left: 10px;
+ margin-right: 10px
+}
+
+.margin-top {
+ margin-top: 20px
+}
+
+.margin-bottom {
+ margin-bottom: 20px
+}
+
+.pull-right {
+ text-align: right
+}
+
+ul.no-bullet li {
+ list-style-type: none;
+ margin-left: 0
+}
+
+#app-loading-icon {
+ position: fixed;
+ right: 3px;
+ bottom: 3px
+}
+
+.assign-me {
+ vertical-align: bottom
+}
+
+a {
+ color: #0366d6;
+ border: none;
+ text-decoration: none;
+}
+
+a:focus {
+ outline: 0;
+ color: #0366d6;
+ text-decoration: none
+}
+
+a:hover {
+ color: #333;
+ text-decoration: none
+}
+
+a .fa {
+ padding-right: 3px;
+ text-decoration: none;
+ color: #acafb1
+}
+
+h1,
+h2,
+h3 {
+ font-weight: normal;
+ color: #333
+}
+
+h1 {
+ font-size: 1.5em
+}
+
+h2 {
+ font-size: 1.4em;
+ margin-bottom: 10px
+}
+
+h3 {
+ margin-top: 10px;
+ font-size: 1.2em
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+ border-spacing: 0;
+ margin-bottom: 20px
+}
+
+table.table-fixed {
+ table-layout: fixed;
+ white-space: nowrap
+}
+
+table.table-fixed th {
+ overflow: hidden
+}
+
+table.table-fixed td {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis
+}
+
+table.table-small {
+ font-size: 0.8em
+}
+
+table.table-striped tr:nth-child(odd) {
+ background: #fefefe
+}
+
+@media (max-width: 768px) {
+ table.table-scrolling {
+ overflow-x: auto;
+ display: inline-block;
+ vertical-align: top;
+ max-width: 100%;
+ white-space: nowrap
+ }
+}
+
+table th {
+ text-align: left;
+ padding: 0.5em 5px;
+ border: 1px solid #eee;
+ background: #fafbfc;
+}
+
+table th a {
+ text-decoration: none;
+ color: #333
+}
+
+table th a:focus,
+table th a:hover {
+ text-decoration: underline
+}
+
+table td {
+ border: 1px solid #f5f5f5;
+ padding: 0.5em 7px;
+ vertical-align: top
+}
+
+table td li {
+ margin-left: 20px
+}
+
+.column-1 {
+ width: 1%
+}
+
+.column-2 {
+ width: 2%
+}
+
+.column-3 {
+ width: 3%
+}
+
+.column-4 {
+ width: 4%
+}
+
+.column-5 {
+ width: 5%
+}
+
+.column-6 {
+ width: 6%
+}
+
+.column-7 {
+ width: 7%
+}
+
+.column-8 {
+ width: 8%
+}
+
+.column-9 {
+ width: 9%
+}
+
+.column-10 {
+ width: 10%
+}
+
+.column-11 {
+ width: 11%
+}
+
+.column-12 {
+ width: 12%
+}
+
+.column-13 {
+ width: 13%
+}
+
+.column-14 {
+ width: 14%
+}
+
+.column-15 {
+ width: 15%
+}
+
+.column-16 {
+ width: 16%
+}
+
+.column-17 {
+ width: 17%
+}
+
+.column-18 {
+ width: 18%
+}
+
+.column-19 {
+ width: 19%
+}
+
+.column-20 {
+ width: 20%
+}
+
+.column-21 {
+ width: 21%
+}
+
+.column-22 {
+ width: 22%
+}
+
+.column-23 {
+ width: 23%
+}
+
+.column-24 {
+ width: 24%
+}
+
+.column-25 {
+ width: 25%
+}
+
+.column-26 {
+ width: 26%
+}
+
+.column-27 {
+ width: 27%
+}
+
+.column-28 {
+ width: 28%
+}
+
+.column-29 {
+ width: 29%
+}
+
+.column-30 {
+ width: 30%
+}
+
+.column-31 {
+ width: 31%
+}
+
+.column-32 {
+ width: 32%
+}
+
+.column-33 {
+ width: 33%
+}
+
+.column-34 {
+ width: 34%
+}
+
+.column-35 {
+ width: 35%
+}
+
+.column-36 {
+ width: 36%
+}
+
+.column-37 {
+ width: 37%
+}
+
+.column-38 {
+ width: 38%
+}
+
+.column-39 {
+ width: 39%
+}
+
+.column-40 {
+ width: 40%
+}
+
+.column-41 {
+ width: 41%
+}
+
+.column-42 {
+ width: 42%
+}
+
+.column-43 {
+ width: 43%
+}
+
+.column-44 {
+ width: 44%
+}
+
+.column-45 {
+ width: 45%
+}
+
+.column-46 {
+ width: 46%
+}
+
+.column-47 {
+ width: 47%
+}
+
+.column-48 {
+ width: 48%
+}
+
+.column-49 {
+ width: 49%
+}
+
+.column-50 {
+ width: 50%
+}
+
+.column-51 {
+ width: 51%
+}
+
+.column-52 {
+ width: 52%
+}
+
+.column-53 {
+ width: 53%
+}
+
+.column-54 {
+ width: 54%
+}
+
+.column-55 {
+ width: 55%
+}
+
+.column-56 {
+ width: 56%
+}
+
+.column-57 {
+ width: 57%
+}
+
+.column-58 {
+ width: 58%
+}
+
+.column-59 {
+ width: 59%
+}
+
+.column-60 {
+ width: 60%
+}
+
+.column-61 {
+ width: 61%
+}
+
+.column-62 {
+ width: 62%
+}
+
+.column-63 {
+ width: 63%
+}
+
+.column-64 {
+ width: 64%
+}
+
+.column-65 {
+ width: 65%
+}
+
+.column-66 {
+ width: 66%
+}
+
+.column-67 {
+ width: 67%
+}
+
+.column-68 {
+ width: 68%
+}
+
+.column-69 {
+ width: 69%
+}
+
+.column-70 {
+ width: 70%
+}
+
+.column-71 {
+ width: 71%
+}
+
+.column-72 {
+ width: 72%
+}
+
+.column-73 {
+ width: 73%
+}
+
+.column-74 {
+ width: 74%
+}
+
+.column-75 {
+ width: 75%
+}
+
+.column-76 {
+ width: 76%
+}
+
+.column-77 {
+ width: 77%
+}
+
+.column-78 {
+ width: 78%
+}
+
+.column-79 {
+ width: 79%
+}
+
+.column-80 {
+ width: 80%
+}
+
+.column-81 {
+ width: 81%
+}
+
+.column-82 {
+ width: 82%
+}
+
+.column-83 {
+ width: 83%
+}
+
+.column-84 {
+ width: 84%
+}
+
+.column-85 {
+ width: 85%
+}
+
+.column-86 {
+ width: 86%
+}
+
+.column-87 {
+ width: 87%
+}
+
+.column-88 {
+ width: 88%
+}
+
+.column-89 {
+ width: 89%
+}
+
+.column-90 {
+ width: 90%
+}
+
+.column-91 {
+ width: 91%
+}
+
+.column-92 {
+ width: 92%
+}
+
+.column-93 {
+ width: 93%
+}
+
+.column-94 {
+ width: 94%
+}
+
+.column-95 {
+ width: 95%
+}
+
+.column-96 {
+ width: 96%
+}
+
+.column-97 {
+ width: 97%
+}
+
+.column-98 {
+ width: 98%
+}
+
+.column-99 {
+ width: 99%
+}
+
+.column-100 {
+ width: 100%
+}
+
+.draggable-row-handle {
+ cursor: move;
+ color: #dedede
+}
+
+.draggable-row-handle:hover {
+ color: #333
+}
+
+tr.draggable-item-selected {
+ background: #fff;
+ border: 2px solid #666;
+ box-shadow: 4px 2px 10px -4px rgba(0, 0, 0, 0.55)
+}
+
+tr.draggable-item-selected td {
+ border-top: none;
+ border-bottom: none
+}
+
+tr.draggable-item-selected td:first-child {
+ border-left: none
+}
+
+tr.draggable-item-selected td:last-child {
+ border-right: none
+}
+
+.table-stripped tr.draggable-item-hover,
+.table-stripped tr.draggable-item-hover {
+ background: #FEFFF2
+}
+
+.table-list {
+ font-size: 0.85em;
+ margin-bottom: 20px
+}
+
+.table-list-header {
+ background: #f6f8fa;
+ border: 1px solid #d1d5da;
+ border-radius: 3px 3px 0px 0px;
+ line-height: 28px;
+ padding-left: 7px;
+ padding-right: 7px;
+}
+
+.table-list-header a {
+ color: #333;
+ font-weight: 500;
+ text-decoration: none;
+ margin-right: 10px
+}
+
+.table-list-header a:hover,
+.table-list-header a:focus {
+ color: #767676
+}
+
+.table-list-header .table-list-header-count {
+ color: #767676;
+ display: inline-block;
+ float: left
+}
+
+.table-list-header .table-list-header-menu {
+ text-align: right
+}
+
+.table-list-row {
+ padding-left: 7px;
+ padding-right: 7px;
+ border-bottom: 1px solid #e5e5e5;
+ border-right: 1px solid #e5e5e5
+}
+
+.table-list-row.table-border-left {
+ border-left: 1px solid #e5e5e5
+}
+
+.table-list-row:nth-child(odd) {
+ background: #fefefe
+}
+
+.table-list-row:last-child {
+ border-radius: 0 0 5px 5px
+}
+
+.table-list-row:hover {
+ background: #f6f8fa;
+ border-bottom: 1px solid #f6f8fa;
+ border-right: 1px solid #f6f8fa
+}
+
+.table-list-row .table-list-title {
+ font-weight: 500;
+ line-height: 23px
+}
+
+.table-list-row .table-list-title.status-closed {
+ text-decoration: line-through;
+ margin-right: 10px
+}
+
+.table-list-row .table-list-title.status-closed a {
+ font-style: italic
+}
+
+.table-list-row .table-list-title a {
+ color: #333;
+ text-decoration: none
+}
+
+.table-list-row .table-list-title a:hover,
+.table-list-row .table-list-title a:focus {
+ text-decoration: underline
+}
+
+.table-list-row .table-list-details {
+ color: #999;
+ font-weight: 300;
+ line-height: 20px
+}
+
+.table-list-row .table-list-details span {
+ margin-left: 5px
+}
+
+.table-list-row .table-list-details span:first-child {
+ margin-left: 0
+}
+
+.table-list-row .table-list-details li {
+ display: inline;
+ list-style-type: none
+}
+
+.table-list-row .table-list-details li:after {
+ content: ', '
+}
+
+.table-list-row .table-list-details li:last-child:after {
+ content: ''
+}
+
+.table-list-row .table-list-details strong {
+ font-weight: 400;
+ color: #555
+}
+
+.table-list-row .table-list-details-with-icons {
+ float: left
+}
+
+@media (max-width: 768px) {
+ .table-list-row .table-list-details-with-icons {
+ float: none
+ }
+}
+
+.table-list-row .table-list-icons {
+ font-size: 0.8em;
+ text-align: right;
+ line-height: 30px
+}
+
+@media (max-width: 768px) {
+ .table-list-row .table-list-icons {
+ text-align: left;
+ line-height: 20px
+ }
+}
+
+.table-list-row .table-list-icons span {
+ margin-left: 5px
+}
+
+.table-list-row .table-list-icons a {
+ text-decoration: none
+}
+
+.table-list-row .table-list-icons a:hover {
+ color: #333
+}
+
+.table-list-row .table-list-icons a:hover i {
+ color: #333
+}
+
+.table-list-category {
+ font-size: 0.9em;
+ font-weight: 500;
+ color: #000;
+ padding: 1px 2px 1px 2px;
+ border-radius: 3px;
+ background: #fcfcfc;
+ border: 1px solid #ccc
+}
+
+.table-list-category a {
+ text-decoration: none;
+ color: #000
+}
+
+.table-list-category a:hover {
+ color: #0366d6
+}
+
+fieldset {
+ border: 1px solid #ddd;
+ margin-top: 10px
+}
+
+legend {
+ font-weight: 500;
+ font-size: 1.2em
+}
+
+label {
+ cursor: pointer;
+ display: block;
+ margin-top: 10px;
+ font-weight: 400
+}
+
+.select2-container--default .select2-selection--multiple .select2-selection__choice {
+ background-color: #f9f9f9;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ cursor: default;
+ float: left;
+ margin-right: 5px;
+ margin-top: 5px;
+ padding: 0 5px;
+}
+
+input[type="number"],
+input[type="date"],
+input[type="email"],
+input[type="password"],
+input[type="text"]:not(.input-addon-field) {
+ color: #999;
+ border: 1px solid #ccc;
+ width: 300px;
+ max-width: 95%;
+ font-size: 1em;
+ height: 25px;
+ border-radius: 3px;
+ padding-bottom: 0;
+ padding-left: 4px;
+ font-family: sans-serif;
+ -webkit-appearance: none;
+ -moz-appearance: none
+}
+
+input[type="number"]::-webkit-input-placeholder,
+input[type="date"]::-webkit-input-placeholder,
+input[type="email"]::-webkit-input-placeholder,
+input[type="password"]::-webkit-input-placeholder,
+input[type="text"]:not(.input-addon-field)::-webkit-input-placeholder {
+ color: #dedede
+}
+
+input[type="number"]::-moz-placeholder,
+input[type="date"]::-moz-placeholder,
+input[type="email"]::-moz-placeholder,
+input[type="password"]::-moz-placeholder,
+input[type="text"]:not(.input-addon-field)::-moz-placeholder {
+ color: #dedede
+}
+
+input[type="number"]:-ms-input-placeholder,
+input[type="date"]:-ms-input-placeholder,
+input[type="email"]:-ms-input-placeholder,
+input[type="password"]:-ms-input-placeholder,
+input[type="text"]:not(.input-addon-field):-ms-input-placeholder {
+ color: #dedede
+}
+
+input[type="number"]:focus,
+input[type="date"]:focus,
+input[type="email"]:focus,
+input[type="password"]:focus,
+input[type="text"]:focus {
+ color: #000;
+ border-color: rgba(82, 168, 236, 0.8);
+ outline: 0;
+ box-shadow: 0 0 0 2px rgba(222,222,222,0.25)
+}
+
+input[type="number"] {
+ width: 70px
+}
+
+input[type="text"]:not(.input-addon-field).form-numeric {
+ width: 70px
+}
+
+input[type="text"]:not(.input-addon-field).form-datetime,
+input[type="text"]:not(.input-addon-field).form-date {
+ width: 150px
+}
+
+input[type="text"]:not(.input-addon-field).form-input-large {
+ width: 400px
+}
+
+input[type="text"]:not(.input-addon-field).form-input-small {
+ width: 150px
+}
+
+textarea:focus {
+ color: #000;
+ border-color: rgba(82, 168, 236, 0.8);
+ outline: 0;
+ box-shadow: 0 0 0 2px rgba(222,222,222,0.25)
+}
+
+.text-editor-toolbar {
+ margin-bottom: 10px!important;
+}
+
+textarea {
+ padding: 4px;
+ border: 1px solid #ccc;
+ width: 400px;
+ max-width: 99%;
+ height: 200px;
+ border-radius: 3px;
+ font-family: sans-serif;
+ font-size: 1em
+}
+
+textarea::-webkit-input-placeholder {
+ color: #dedede
+}
+
+textarea::-moz-placeholder {
+ color: #dedede
+}
+
+textarea:-ms-input-placeholder {
+ color: #dedede
+}
+
+select {
+ font-size: 1.0em;
+ max-width: 95%;
+ border: 1px solid #AAA;
+ color: #555;
+ font-size: inherit;
+ overflow: hidden;
+ border-radius: 3px;
+ padding: 5px 10px;
+ background-color: #fff;
+}
+
+select:focus {
+ outline: 0
+}
+
+select[multiple] {
+ width: 300px
+}
+
+.tag-autocomplete {
+ width: 400px
+}
+
+span.select2-container {
+ margin-top: 2px
+}
+
+.form-actions {
+ padding-top: 20px;
+ clear: both
+}
+
+.form-required {
+ color: red;
+ padding-left: 5px;
+ font-weight: bold
+}
+
+@media (max-width: 480px) {
+ .form-required {
+ display: none
+ }
+}
+
+input[type="text"].form-max-width {
+ width: 100%
+}
+
+input.form-error,
+textarea.form-error {
+ border: 2px solid #b94a48
+}
+
+input.form-error:focus,
+textarea.form-error:focus {
+ box-shadow: none;
+ border: 2px solid #b94a48
+}
+
+.form-errors {
+ color: #b94a48;
+ list-style-type: none
+}
+
+ul.form-errors li {
+ margin-left: 0
+}
+
+.form-help {
+ font-size: 0.8em;
+ color: brown;
+ margin-bottom: 15px
+}
+
+.form-inline {
+ padding: 0;
+ margin: 0;
+ border: none
+}
+
+.form-inline label {
+ display: inline;
+ padding-right: 3px
+}
+
+.form-inline input,
+.form-inline select {
+ margin: 0 15px 0 0
+}
+
+.form-inline .form-required {
+ display: none
+}
+
+.form-inline .form-actions {
+ display: inline-block
+}
+
+.form-inline .js-submit-buttons-rendered {
+ display: inline-block
+}
+
+.form-inline-group {
+ display: inline
+}
+
+.form-columns {
+ display: -webkit-flex;
+ display: flex;
+ -webkit-flex-direction: row;
+ flex-direction: row;
+ -webkit-flex-wrap: wrap;
+ flex-wrap: wrap;
+ -webkit-justify-content: flex-start;
+ justify-content: flex-start
+}
+
+.form-columns .form-column {
+ margin-right: 25px;
+ flex-grow: 1
+}
+
+.form-columns fieldset {
+ margin-top: 0
+}
+
+.form-login {
+ max-width: 350px;
+ margin: 5% auto 0
+}
+
+@media (max-width: 480px) {
+ .form-login {
+ margin-left: 5px
+ }
+}
+
+.form-login li {
+ margin-left: 25px;
+ line-height: 25px
+}
+
+.form-login h2 {
+ margin-bottom: 30px;
+ font-weight: bold
+}
+
+.reset-password {
+ margin-top: 20px;
+ margin-bottom: 20px
+}
+
+.reset-password a {
+ color: #999
+}
+
+.input-addon {
+ display: flex
+}
+
+.input-addon-field {
+ flex: 1;
+ font-size: 1em;
+ color: #999;
+ margin: 0;
+ -webkit-appearance: none;
+ -moz-appearance: none
+}
+
+.input-addon-field:first-child {
+ border-radius: 5px 0 0 5px
+}
+
+.input-addon-field:last-child {
+ border-radius: 0 5px 5px 0
+}
+
+.input-addon-item {
+ background-image: linear-gradient(-180deg, #fafbfc 0%, #eff3f6 90%);
+ color: #666;
+ font: inherit;
+ font-weight: normal;
+}
+
+.input-addon-item:first-child {
+ border-radius: 5px 0 0 5px
+}
+
+.input-addon-item:last-child {
+ border-radius: 0 5px 5px 0
+}
+
+@media (max-width: 480px) {
+ .input-addon-item .dropdown .fa-caret-down {
+ display: none
+ }
+}
+
+.input-addon-field,
+.input-addon-item {
+ border: 1px solid rgba(147, 128, 108, 0.25);
+ padding: 4px 0.75em
+}
+
+.input-addon-field:not(:first-child),
+.input-addon-item:not(:first-child) {
+ border-left: 0
+}
+
+.icon-success {
+ color: #468847
+}
+
+.icon-error {
+ color: #b94a48
+}
+
+.icon-fade-out {
+ opacity: 1;
+ animation: icon-fadeout 5s linear forwards
+}
+
+@keyframes icon-fadeout {
+ 0% {
+ opacity: 1
+ }
+ 100% {
+ opacity: 0
+ }
+}
+
+.alert {
+ padding: 8px 35px 8px 14px;
+ margin-top: 5px;
+ margin-bottom: 5px;
+ color: #464646;
+ background-color: #FFEB3B;
+ border: 1px solid #f9df00;
+ border-radius: 4px
+}
+
+.alert-success {
+ color: #468847;
+ background-color: #dff0d8;
+ border-color: #d6e9c6
+}
+
+.alert-error {
+ color: #b94a48;
+ background-color: #f2dede;
+ border-color: #eed3d7
+}
+
+.alert-info {
+ color: #464646;
+ background-color: #92ddff;
+ border-color: #83d8ff;
+}
+
+.alert-normal {
+ color: #333;
+ background-color: #f0f0f0;
+ border-color: #ddd
+}
+
+.alert ul {
+ margin-top: 10px;
+ margin-bottom: 10px
+}
+
+.alert li {
+ margin-left: 25px
+}
+
+.alert-fade-out {
+ text-align: center;
+ position: fixed;
+ bottom: 0;
+ left: 20%;
+ width: 60%;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ margin-bottom: 0;
+ border-width: 1px 0 0;
+ border-radius: 4px 4px 0 0;
+ z-index: 9999;
+ opacity: 1;
+ animation: fadeout 5s linear forwards
+}
+
+@keyframes fadeout {
+ 0% {
+ opacity: 1
+ }
+ 100% {
+ opacity: 0
+ }
+}
+
+a.btn {
+ text-decoration: none
+}
+
+.btn {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ font-size: 1.2em;
+ font-weight: normal;
+ cursor: pointer;
+ display: inline-block;
+ border-radius: 2px;
+ padding: 3px 10px;
+ margin: 0;
+ border: 1px solid #ddd;
+ background: #f5f5f5;
+ color: #333
+}
+
+.btn:hover,
+.btn:focus {
+ background-color: #eee;
+ color: #000;
+ border-color: #eee;
+ border-color: #e5e5e5;
+}
+
+.btn-red {
+ border-color: #b0281a;
+ background: #d14836;
+ color: #fff
+}
+
+.btn-red:hover,
+.btn-red:focus {
+ background-color: #28a745;
+ background-image: linear-gradient(-180deg, #34d058 0%, #28a745 90%);
+ color: #fff;
+ border-color: #28a745;
+}
+
+.btn-blue {
+ border-color: #3079ed;
+ background-image: linear-gradient(-180deg, #4d90fe 0%, #335996 90%);
+ color: #fff
+}
+
+.btn-blue:hover,
+.btn-blue:focus {
+ background-color: #28a745;
+ background-image: linear-gradient(-180deg, #34d058 0%, #28a745 90%);
+ color: #fff;
+ border-color: #28a745;
+}
+
+.btn:disabled {
+ color: #ccc;
+ border-color: #ccc;
+ background: #f7f7f7
+}
+
+.buttons-header {
+ font-size: 0.8em;
+ margin-top: 5px;
+ margin-bottom: 15px
+}
+
+.tooltip i.fa {
+ cursor: pointer
+}
+
+.tooltip .fa-info-circle {
+ color: #999
+}
+
+#tooltip-container {
+ padding: 5px;
+ background: #fff;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ box-shadow: 0 6px 12px #aaa;
+ position: absolute;
+ min-width: 350px
+}
+
+#tooltip-container .markdown p {
+ margin-bottom: 0
+}
+
+#tooltip-container .tooltip-large {
+ width: 600px
+}
+
+h2 .dropdown ul {
+ display: none
+}
+
+.dropdown {
+ display: inline;
+ position: relative
+}
+
+.dropdown ul {
+ display: none
+}
+
+.dropdown-smaller {
+ font-size: 0.85em
+}
+
+ul.dropdown-submenu-open {
+ display: block;
+ position: absolute;
+ z-index: 1000;
+ min-width: 285px;
+ list-style: none;
+ margin: 3px 0 0 1px;
+ padding: 6px 0;
+ background-color: #fff;
+ border: 1px solid #b2b2b2;
+ border-radius: 3px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15)
+}
+
+.dropdown-submenu-open li {
+ display: block;
+ margin: 0;
+ padding: 9px 20px;
+ font-size: 0.9em;
+ border-bottom: none;
+ cursor: pointer;
+}
+
+.dropdown-submenu-open li.no-hover {
+ cursor: default
+}
+
+.dropdown-submenu-open li:last-child {
+ border: none
+}
+
+.dropdown-submenu-open li:not(.no-hover):hover {
+ background: #f6f8fa;
+ color: #8d9498
+}
+
+.dropdown-submenu-open li:hover a {
+ color: #262626
+}
+
+.dropdown-submenu-open a {
+ text-decoration: none;
+ color: #333
+}
+
+.dropdown-submenu-open a:focus {
+ text-decoration: underline
+}
+
+.dropdown-menu-link-text,
+.dropdown-menu-link-icon {
+ color: #333;
+ text-decoration: none
+}
+
+.dropdown-menu-link-text:hover {
+ text-decoration: underline
+}
+
+td a.dropdown-menu strong {
+ color: #cb2431
+}
+
+td a.dropdown-menu strong i {
+ color: #cb2431
+}
+
+td a.dropdown-menu i {
+ color: #dedede
+}
+
+td a.dropdown-menu:hover strong {
+ color: #555
+}
+
+td a.dropdown-menu:hover strong i {
+ color: #555
+}
+
+td a.dropdown-menu:hover i {
+ color: #333
+}
+
+.accordion-title {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAADCAYAAABS3WWCAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NEQ5RDgxQzc2RjQ5MTFFMjhEMUNENzFGRUMwRjhBRTciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NEQ5RDgxQzg2RjQ5MTFFMjhEMUNENzFGRUMwRjhBRTciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0RDlEODFDNTZGNDkxMUUyOEQxQ0Q3MUZFQzBGOEFFNyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0RDlEODFDNjZGNDkxMUUyOEQxQ0Q3MUZFQzBGOEFFNyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvXFWFAAAAAYSURBVHjaYvj//z8D0/Pnz/8zgFgAAQYAS5UJscReGMIAAAAASUVORK5CYII=) repeat-x scroll 0 10px
+}
+
+.accordion-title h3 {
+ display: inline;
+ padding-right: 5px;
+ background: #fff
+}
+
+.accordion-content {
+ margin-top: 15px;
+ margin-bottom: 25px
+}
+
+.accordion-toggle {
+ color: #333;
+ text-decoration: none
+}
+
+.accordion-toggle:focus {
+ color: #333
+}
+
+.accordion-toggle:hover {
+ color: #999
+}
+
+.accordion-toggle:before {
+ content: "\f0d7"
+}
+
+.accordion-collapsed {
+ margin-bottom: 25px
+}
+
+.accordion-collapsed .accordion-toggle:before {
+ content: "\f0da"
+}
+
+.accordion-collapsed .accordion-content {
+ display: none
+}
+
+#select-dropdown-menu {
+ position: absolute;
+ display: block;
+ z-index: 1000;
+ min-width: 160px;
+ padding: 5px 0;
+ background: #fff;
+ list-style: none;
+ border: 1px solid #ccc;
+ border-radius: 3px;
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+ overflow-x: hidden;
+}
+
+.select-dropdown-menu-item {
+ white-space: nowrap;
+ overflow: hidden;
+ padding: 3px 10px;
+ color: #555;
+ cursor: pointer;
+ border-bottom: 1px solid #f8f8f8;
+ line-height: 1.5em;
+ font-weight: 400
+}
+
+.select-dropdown-menu-item.active {
+ color: #fff;
+ background: #0366d6
+}
+
+.select-dropdown-menu-item:last-child {
+ border: none
+}
+
+.select-dropdown-input-container {
+ position: relative;
+ border: 1px solid #ccc;
+ border-radius: 3px;
+ background-color: #fff;
+ max-width: 300px;
+}
+
+.select-dropdown-input-container input.select-dropdown-input {
+ margin: 0 0 0 5px;
+ border: none;
+ height: 23px
+}
+
+.select-dropdown-input-container input.select-dropdown-input:focus {
+ border: none;
+ box-shadow: none
+}
+
+.select-dropdown-input-container .select-dropdown-chevron {
+ color: #555;
+ position: absolute;
+ top: 4px;
+ right: 5px;
+ cursor: pointer
+}
+
+.select-dropdown-input-container .select-loading-icon {
+ color: #555;
+ position: absolute;
+ top: 4px;
+ right: 5px
+}
+
+#suggest-menu {
+ position: absolute;
+ display: block;
+ z-index: 1000;
+ min-width: 160px;
+ padding: 5px 0;
+ background: #fff;
+ list-style: none;
+ border: 1px solid #ccc;
+ border-radius: 3px;
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175)
+}
+
+.suggest-menu-item {
+ white-space: nowrap;
+ padding: 3px 10px;
+ color: #333;
+ font-weight: bold;
+ cursor: pointer
+}
+
+.suggest-menu-item.active {
+ color: #fff;
+ background: #0366d6
+}
+
+.suggest-menu-item.active small {
+ color: #fff
+}
+
+.suggest-menu-item small {
+ color: #999;
+ font-weight: normal
+}
+
+#modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.9);
+ overflow: auto;
+ z-index: 100
+}
+
+#modal-box {
+ position: fixed;
+ max-height: calc(100% - 30px);
+ top: 0;
+ padding: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ background: #fafbfc;
+ overflow: auto;
+ border-radius: 0px 0px 3px 3px;
+}
+
+#modal-content {
+ padding: 0 5px 5px
+}
+
+#modal-header {
+ text-align: right;
+ padding-right: 5px
+}
+
+#modal-close-button {
+ color: #333
+}
+
+#modal-close-button:hover {
+ color: #b94a48
+}
+
+.pagination {
+ text-align: center;
+ font-size: 0.9em
+}
+
+.pagination-showing {
+ margin-right: 5px;
+ padding-right: 5px;
+ border-right: 1px solid #999
+}
+
+.pagination-next {
+ margin-left: 5px
+}
+
+.pagination-previous {
+ margin-right: 5px
+}
+
+.header img {
+ float: left;
+}
+
+.header h2 {
+ position: relative;
+ color:#fff;
+ top: 16px;
+ left: 10px;
+ margin: 0;
+}
+
+header {
+ display: flex;
+ flex-wrap: wrap;
+ padding: 5px 10px;
+ margin-bottom: 5px;
+ border-bottom: 0px solid #dedede;
+ padding: 15px 10px;
+ margin-bottom: 15px;
+ background: #24292e;
+}
+
+header .title-container {
+ flex: 1;
+ min-width: 300px
+}
+
+@media (max-width: 480px) {
+ header .title-container {
+ order: 3
+ }
+}
+
+header .board-selector-container {
+ min-width: 320px;
+ display: flex;
+ align-items: center
+}
+
+@media (max-width: 480px) {
+ header .board-selector-container {
+ order: 2;
+ min-width: 300px
+ }
+ header .board-selector-container input[type=text] {
+ max-width: 280px
+ }
+}
+
+header .menus-container {
+ min-width: 120px;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end
+}
+
+@media (max-width: 480px) {
+ header .menus-container {
+ order: 1;
+ margin-bottom: 5px;
+ margin-left: auto
+ }
+}
+
+header h1 {
+ font-size: 1.4em!important;
+ color: #dddddd!important
+}
+
+header h1 .tooltip {
+ opacity: 0.3;
+ font-size: 0.7em
+}
+
+a i.web-notification-icon {
+ color: #0366d6
+}
+
+a i.web-notification-icon:focus,
+a i.web-notification-icon:hover {
+ color: #000
+}
+
+.logo a {
+ opacity: 0.5;
+ color: #d40000;
+ text-decoration: none
+}
+
+.logo span {
+ color: #333
+}
+
+.logo a:hover {
+ opacity: 0.8;
+ color: #333
+}
+
+.logo a:focus span,
+.logo a:hover span {
+ color: #d40000
+}
+
+.page-header {
+ margin-bottom: 20px;
+ padding: 10px;
+ background-color: #fafbfc;
+ border-radius: 3px;
+}
+
+.page-header .dropdown {
+ padding-right: 10px
+}
+
+.page-header h2 {
+ margin: 0;
+ padding: 0;
+ font-weight: bold;
+ border-bottom: none;
+}
+
+.page-header h2 a {
+ color: #333;
+ text-decoration: none
+}
+
+.page-header h2 a:focus,
+.page-header h2 a:hover {
+ color: #999
+}
+
+.page-header ul {
+ text-align: left;
+ margin-top: 5px;
+ display: inline-block
+}
+
+.page-header li {
+ display: inline;
+ padding-right: 15px
+}
+
+@media (max-width: 480px) {
+ .page-header li {
+ display: block;
+ line-height: 1.5em
+ }
+}
+
+.page-header li.active a {
+ color: #333;
+ text-decoration: none;
+ font-weight: bold
+}
+
+.page-header li.active a:hover,
+.page-header li.active a:focus {
+ text-decoration: underline
+}
+
+.menu-inline {
+ margin-bottom: 5px
+}
+
+.menu-inline li {
+ display: inline;
+ padding-right: 15px
+}
+
+.menu-inline li .active a {
+ font-weight: bold;
+ color: #000;
+ text-decoration: none
+}
+
+.sidebar-container {
+ height: 100%;
+ display: flex;
+ flex-flow: row
+}
+
+@media (max-width: 768px) {
+ .sidebar-container {
+ flex-flow: wrap
+ }
+}
+
+.sidebar-content {
+ padding-left: 10px;
+ flex: 1 100%;
+ max-width: 85%;
+ overflow-wrap: break-word
+}
+
+@media (max-width: 768px) {
+ .sidebar-content {
+ padding-left: 0;
+ order: 1;
+ max-width: 100%
+ }
+}
+
+@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: landscape) and (-webkit-min-device-pixel-ratio: 1) {
+ .sidebar-content {
+ max-width: 75%
+ }
+}
+
+.sidebar {
+ max-width: 25%;
+ border: 1px solid #d1d5da;
+ min-width: 230px;
+ padding: 8px 10px;
+ border-radius: 3px;
+ background: #fafbfc;
+}
+
+@media (max-width: 768px) {
+ .sidebar {
+ flex: 1 auto;
+ order: 2
+ }
+}
+
+.sidebar h2 {
+ margin-top: 0
+}
+
+.sidebar>ul a {
+ text-decoration: none;
+ color: #586069;
+ font-weight: 500
+}
+
+.sidebar>ul a:hover {
+ color: #23292d
+}
+
+.sidebar>ul li {
+ list-style-type: none;
+ line-height: 35px;
+ border-bottom: 1px dotted #e5e5e5;
+ padding-left: 13px
+}
+
+.sidebar>ul li:hover {
+ border-left: 5px solid #555;
+ padding-left: 8px
+}
+
+.sidebar>ul li.active {
+ border-left: 5px solid #23292d;
+ padding-left: 8px
+}
+
+.sidebar>ul li.active a {
+ color: #23292d;
+ font-weight: bold
+}
+
+.sidebar-icons>ul li {
+ padding-left: 0
+}
+
+.sidebar-icons>ul li:hover,
+.sidebar-icons>ul li.active {
+ padding-left: 0;
+ border-left: none
+}
+
+.sidebar>ul li.active a:focus,
+.sidebar>ul li.active a:hover {
+ color: #555
+}
+
+.sidebar>ul li:last-child {
+ margin-bottom: 15px
+}
+
+.avatar img {
+ vertical-align: bottom
+}
+
+.avatar-left {
+ float: left;
+ margin-right: 10px
+}
+
+.avatar-inline {
+ display: inline-block;
+ margin-right: 3px
+}
+
+.avatar-48 img,
+.avatar-48 div {
+ border-radius: 30px
+}
+
+.avatar-48 .avatar-letter {
+ line-height: 48px;
+ width: 48px;
+ font-size: 25px
+}
+
+.avatar-20 img,
+.avatar-20 div {
+ border-radius: 10px
+}
+
+.avatar-20 .avatar-letter {
+ line-height: 20px;
+ width: 20px;
+ font-size: 11px
+}
+
+.avatar-letter {
+ color: #fff;
+ text-align: center
+}
+
+#file-dropzone,
+#screenshot-zone {
+ position: relative;
+ border: 2px dashed #ccc;
+ width: 99%;
+ height: 250px;
+ overflow: auto
+}
+
+#file-dropzone-inner,
+#screenshot-inner {
+ position: absolute;
+ left: 0;
+ bottom: 48%;
+ width: 100%;
+ text-align: center;
+ color: #aaa
+}
+
+#screenshot-zone.screenshot-pasted {
+ border: 2px solid #333
+}
+
+#file-list {
+ margin: 20px
+}
+
+#file-list li {
+ list-style-type: none;
+ padding-top: 8px;
+ padding-bottom: 8px;
+ border-bottom: 1px dotted #ddd;
+ width: 95%
+}
+
+#file-list li .file-error {
+ font-weight: bold;
+ color: #b94a48
+}
+
+.file-thumbnails {
+ display: -webkit-flex;
+ display: flex;
+ -webkit-flex-direction: row;
+ flex-direction: row;
+ -webkit-flex-wrap: wrap;
+ flex-wrap: wrap;
+ -webkit-justify-content: flex-start;
+ justify-content: flex-start
+}
+
+.file-thumbnail {
+ width: 250px;
+ border: 1px solid #efefef;
+ border-radius: 5px;
+ margin-bottom: 20px;
+ box-shadow: 4px 2px 10px -6px rgba(0, 0, 0, 0.55);
+ margin-right: 15px
+}
+
+.file-thumbnail img {
+ cursor: pointer;
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px
+}
+
+.file-thumbnail img:hover {
+ opacity: 0.5
+}
+
+.file-thumbnail-content {
+ padding-left: 8px;
+ padding-right: 8px
+}
+
+.file-thumbnail-title {
+ font-weight: 700;
+ font-size: 0.9em;
+ color: #555;
+ overflow: hidden;
+ text-overflow: ellipsis
+}
+
+.file-thumbnail-description {
+ font-size: 0.8em;
+ color: #999;
+ margin-top: 8px;
+ margin-bottom: 5px
+}
+
+.file-viewer {
+ position: relative
+}
+
+.file-viewer img {
+ max-width: 95%;
+ max-height: 85%;
+ margin-top: 10px
+}
+
+.color-picker {
+ width: 180px
+}
+
+.color-picker-option {
+ height: 25px
+}
+
+.color-picker-square {
+ display: inline-block;
+ width: 18px;
+ height: 18px;
+ margin-right: 5px;
+ border: 1px solid #000
+}
+
+.color-picker-label {
+ display: inline-block;
+ vertical-align: bottom;
+ padding-bottom: 3px
+}
+
+.filter-box {
+ max-width: 1024px
+}
+
+.action-menu {
+ color: #333;
+ text-decoration: none
+}
+
+.action-menu:hover,
+.action-menu:focus {
+ text-decoration: underline
+}
+
+.js-project-creation-options {
+ max-width: 500px;
+ border-left: 3px solid #efefef;
+ margin-top: 20px;
+ padding-left: 15px;
+ padding-bottom: 5px;
+ padding-top: 5px
+}
+
+.project-overview-columns {
+ display: -webkit-flex;
+ display: flex;
+ -webkit-flex-direction: row;
+ flex-direction: row;
+ -webkit-flex-wrap: wrap;
+ flex-wrap: wrap;
+ -webkit-align-items: center;
+ align-items: center;
+ -webkit-justify-content: center;
+ justify-content: center;
+ margin-bottom: 20px;
+ font-size: 1.4em
+}
+
+@media (max-width: 480px) {
+ .project-overview-columns {
+ display: block
+ }
+}
+
+.project-overview-column {
+ text-align: center;
+ margin-right: 3%;
+ margin-top: 5px;
+ padding: 3px 15px 3px 15px;
+ border: 1px dashed #ddd
+}
+
+@media (max-width: 480px) {
+ .project-overview-column {
+ text-align: left
+ }
+}
+
+.project-overview-column small {
+ color: #999
+}
+
+.project-overview-column strong {
+ color: #555;
+ display: block
+}
+
+@media (max-width: 480px) {
+ .project-overview-column strong {
+ display: inline
+ }
+}
+
+.project-header {
+ margin-bottom: 8px
+}
+
+.project-header .dropdown-component {
+ margin-top: 4px;
+ margin-right: 5px;
+ float: left
+}
+
+@media (max-width: 768px) {
+ .project-header .dropdown-component {
+ float: none
+ }
+}
+
+.project-header .views-switcher-component {
+ margin-top: 4px;
+ float: left
+}
+
+@media (max-width: 768px) {
+ .project-header .views-switcher-component {
+ float: none;
+ margin-bottom: 10px
+ }
+}
+
+.project-header .filter-box-component form {
+ margin: 0
+}
+
+.views {
+ margin-right: 10px;
+ margin-top: 1px;
+ font-size: 0.9em
+}
+
+@media (max-width: 560px) {
+ .views {
+ width: 100%
+ }
+}
+
+@media (max-width: 768px) {
+ .views {
+ margin-top: 10px;
+ font-size: 1em
+ }
+}
+
+@media (max-width: 480px) {
+ .views {
+ margin-top: 5px
+ }
+}
+
+.views li {
+ white-space: nowrap;
+ background-color: #e6ebf1;
+ background-image: linear-gradient(-180deg, #f0f3f6 0%, #e6ebf1 90%);
+ background-position: -.5em;
+ border-color: rgba(27,31,35,0.35);
+ border: 1px solid #ddd;
+ border-right: none;
+ padding: 4px 8px;
+ display: inline;
+}
+
+.views li:hover {
+ white-space: nowrap;
+ background-image: linear-gradient(-180deg, #fafbfc 0%, #eff3f6 90%);
+ border: 1px solid #ddd;
+ border-right: none;
+ padding: 4px 8px;
+ display: inline;
+}
+
+@media (max-width: 560px) {
+ .views li {
+ display: block;
+ margin-top: 5px;
+ border-radius: 5px;
+ border: 1px solid #ddd
+ }
+}
+
+.views li.active a {
+ font-weight: bold;
+ color: #000;
+ text-decoration: none
+}
+
+.views li:first-child {
+ border-top-left-radius: 5px;
+ border-bottom-left-radius: 5px
+}
+
+.views li:last-child {
+ border-right: 1px solid #ddd;
+ border-top-right-radius: 5px;
+ border-bottom-right-radius: 5px
+}
+
+.views a {
+ color: #555;
+ text-decoration: none
+}
+
+.views a:hover {
+ color: #333;
+ text-decoration: none;
+}
+
+.dashboard-project-stats small {
+ margin-right: 10px;
+ color: #999
+}
+
+.dashboard-table-link {
+ font-weight: bold;
+ color: #000;
+ text-decoration: none
+}
+
+.dashboard-table-link:focus,
+.dashboard-table-link:hover {
+ color: #999
+}
+
+.public-board {
+ margin-top: 5px
+}
+
+.public-task {
+ max-width: 800px;
+ margin: 5px auto 0
+}
+
+#board-container {
+ overflow-x: auto
+}
+
+#board {
+ table-layout: fixed;
+ margin-bottom: 0
+}
+
+#board th.board-column-header {
+ width: 240px
+}
+
+#board td {
+ vertical-align: top
+}
+
+.board-container-compact {
+ overflow-x: initial
+}
+
+@media all and (-ms-high-contrast: active),
+(-ms-high-contrast: none) {
+ .board-container-compact #board {
+ table-layout: auto
+ }
+}
+
+#board th.board-column-header.board-column-compact {
+ width: initial
+}
+
+.board-column-collapsed {
+ display: none
+}
+
+td.board-column-task-collapsed {
+ font-weight: bold;
+ background-color: #f6f8fa
+}
+
+#board th.board-column-header-collapsed {
+ width: 28px;
+ min-width: 28px;
+ text-align: center;
+ overflow: hidden
+}
+
+.board-rotation-wrapper {
+ position: relative;
+ padding: 8px 4px;
+ min-height: 150px;
+ overflow: hidden
+}
+
+.board-rotation {
+ white-space: nowrap;
+ -webkit-backface-visibility: hidden;
+ -webkit-transform: rotate(90deg);
+ -moz-transform: rotate(90deg);
+ -ms-transform: rotate(90deg);
+ transform: rotate(90deg);
+ -webkit-transform-origin: 0 100%;
+ -moz-transform-origin: 0 100%;
+ -ms-transform-origin: 0 100%;
+ transform-origin: 0 100%
+}
+
+.board-column-title .dropdown-menu {
+ text-decoration: none
+}
+
+.board-add-icon {
+ float: left;
+ padding: 0 5px
+}
+
+.board-add-icon i {
+ text-decoration: none;
+ color: #279f43;
+ font-size: 1.4em
+}
+
+.board-add-icon i:focus,
+.board-add-icon i:hover {
+ text-decoration: none;
+ color: #23292d
+}
+
+.board-column-header-task-count {
+ color: #999;
+ font-weight: normal
+}
+
+a.board-swimlane-toggle {
+ text-decoration: none
+}
+
+a.board-swimlane-toggle:hover,
+a.board-swimlane-toggle:focus {
+ color: #000;
+ text-decoration: none;
+ border: none
+}
+
+.board-task-list {
+ min-height: 60px
+}
+
+.board-task-list .task-board:last-child {
+ margin-bottom: 0
+}
+
+.board-task-list-limit {
+ background-color: #DF5353
+}
+
+.draggable-item {
+ cursor: pointer;
+ user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none
+}
+
+.draggable-placeholder {
+ border: 2px dashed #000;
+ background: #fafafa;
+ height: 70px;
+ margin-bottom: 10px
+}
+
+div.draggable-item-selected {
+ border: 1px solid #000
+}
+
+.task-board-sort-handle {
+ float: left;
+ padding-right: 5px
+}
+
+.task-board {
+ margin-bottom: 8px;
+ padding: 12px;
+ border-radius: 3px;
+ box-shadow: 0px 1px 1px 0 rgba(46, 61, 73, 0.30);
+ border-bottom: none;
+ border-left: none;
+ border-right: 2px solid;
+ border-top: none;
+ background-color: #f9f9f9!important;
+}
+
+div.task-board-recent {
+ border-width: 2px
+}
+
+div.task-board-status-closed {
+ user-select: none;
+ border: 1px dotted #555
+}
+
+.task-board a {
+ color: #0366d6;
+ font-weight: 600;
+ text-decoration: none
+}
+
+.task-board-collapsed {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis
+}
+
+.task-board-title {
+ margin-top: 5px;
+ margin-bottom: 8px
+}
+
+.task-board-title a:hover {
+ text-decoration: underline
+}
+
+.task-board-saving-state {
+ opacity: 0.3
+}
+
+.task-board-saving-icon {
+ position: absolute;
+ margin: auto;
+ width: 100%;
+ text-align: center;
+ color: #000
+}
+
+.task-board-avatars {
+ text-align: right;
+ float: right
+}
+
+.task-board-change-assignee {
+ cursor: pointer
+}
+
+.task-board-change-assignee:hover {
+ opacity: 0.6
+}
+
+.task-list-avatars {
+ display: inline-block;
+ float: left
+}
+
+@media (max-width: 768px) {
+ .task-list-avatars {
+ float: none;
+ display: block
+ }
+}
+
+.task-list-avatars .task-avatar-assignee {
+ font-weight: 300;
+ color: #999
+}
+
+.task-list-avatars:hover .task-avatar-assignee {
+ font-weight: 400;
+ color: #000
+}
+
+.task-board-icons,
+.task-list-icons {
+ font-size: 0.8em;
+ text-align: right
+}
+
+.task-board-icons a,
+.task-list-icons a {
+ text-decoration: none
+}
+
+.task-board-icons a:hover,
+.task-list-icons a:hover {
+ color: #333
+}
+
+.task-board-icons a:hover i,
+.task-list-icons a:hover i {
+ color: #333
+}
+
+.task-board-icons .task-score,
+.task-list-icons .task-score {
+ font-weight: bold
+}
+
+.task-board-icons .flag-milestone,
+.task-list-icons .flag-milestone {
+ color: green
+}
+
+.task-board-icons {
+ margin-top: 7px
+}
+
+.task-board-icons a {
+ opacity: 0.5
+}
+
+.task-board-icons span {
+ opacity: 0.5;
+ color: #1e252a;
+ margin-left: 4px
+}
+
+.task-board-icons a:hover {
+ opacity: 1.0;
+ font-weight: bold
+}
+
+.task-board-icons .task-board-icons-row {
+ line-height: 22px
+}
+
+.task-list-icons {
+ line-height: 22px
+}
+
+.task-list-icons a,
+.task-list-icons span,
+.task-list-icons i {
+ color: #999;
+ opacity: 1.0
+}
+
+.task-list-icons span {
+ margin-left: 5px
+}
+
+@media (max-width: 768px) {
+ .task-list-icons {
+ text-align: left
+ }
+}
+
+.task-icon-age {
+ display: inline-block
+}
+
+span.task-icon-age-total {
+ border: 1px solid #e5e5e5;
+ padding: 1px 3px 1px 3px;
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px
+}
+
+span.task-icon-age-column {
+ border: 1px solid #e5e5e5;
+ border-left: none;
+ margin-left: -5px;
+ padding: 1px 3px 1px 3px;
+ border-top-right-radius: 3px;
+ border-bottom-right-radius: 3px
+}
+
+.task-board span.task-icon-age-total,
+.task-board span.task-icon-age-column {
+ border-color: #666
+}
+
+.task-board-category-container {
+ text-align: right;
+ margin-top: 8px;
+ margin-bottom: 8px
+}
+
+.task-board-category {
+ border: 1px solid #555;
+ font-size: 0.9em;
+ font-weight: 500;
+ color: #000;
+ padding: 1px 3px 1px 2px;
+ border-radius: 3px
+}
+
+.task-board-category a:hover {
+ text-decoration: underline
+}
+
+.task-date {
+ font-weight: 500;
+ color: #000
+}
+
+span.task-date-today {
+ opacity: 1.0;
+ color: #0366d6
+}
+
+span.task-date-overdue {
+ opacity: 1.0;
+ color: #b94a48
+}
+
+.task-tags li {
+ display: inline-block;
+ margin: 3px 3px 0 0;
+ padding: 1px 3px 1px 3px;
+ color: #333;
+ border: 1px solid #333;
+ border-radius: 4px
+}
+
+.task-summary-container .task-tags {
+ margin-top: 10px
+}
+
+.task-list-tag {
+ padding: 0.1em 0.9em;
+ margin: 0 0.5em 0.5em 0;
+ white-space: nowrap;
+ background-color: #f9f9f9;
+ border-radius: 3px;
+}
+
+#task-summary {
+ margin-bottom: 15px
+}
+
+#task-summary h2 {
+ color: #555;
+ font-size: 1.6em;
+ margin-top: 0;
+ padding-top: 0
+}
+
+.task-summary-container {
+ border: none;
+ border-radius: 3px;
+ border-bottom: 5px solid;
+ padding: 20px;
+}
+
+.task-summary-columns {
+ display: flex;
+ flex-flow: row;
+ justify-content: space-between
+}
+
+@media (max-width: 768px) {
+ .task-summary-columns {
+ flex-flow: column
+ }
+}
+
+.task-summary-column {
+ color: #333
+}
+
+.task-summary-column span {
+ color: #555
+}
+
+.task-summary-column li {
+ line-height: 23px
+}
+
+#external-task-view {
+ padding: 10px;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ border: 1px dotted #ccc
+}
+
+.task-form-container {
+ box-sizing: border-box;
+ display: flex;
+ flex-wrap: wrap
+}
+
+.task-form-container>* {
+ box-sizing: border-box
+}
+
+.task-form-container>* {
+ width: 1%
+}
+
+.task-form-main-column {
+ width: 60%
+}
+
+@media (max-width: 1000px) {
+ .task-form-main-column {
+ width: 100%
+ }
+}
+
+.task-form-main-column input[type="text"] {
+ width: 700px;
+ max-width: 99%
+}
+
+.task-form-secondary-column {
+ max-width: 250px;
+ min-width: 200px;
+ max-height: 600px;
+ padding-left: 10px;
+ overflow: auto;
+ width: 20%
+}
+
+@media (max-width: 1000px) {
+ .task-form-secondary-column {
+ width: 100%;
+ max-width: 99%;
+ max-height: none
+ }
+}
+
+@media (max-width: 768px) {
+ .task-form-secondary-column {
+ padding-left: 0
+ }
+}
+
+.task-form-secondary-column label:first-child {
+ margin-top: 0
+}
+
+@media (max-width: 1000px) {
+ .task-form-secondary-column label:first-child {
+ margin-top: 10px
+ }
+}
+
+.task-form-bottom {
+ width: 100%
+}
+
+.comment-sorting {
+ text-align: right
+}
+
+.comment-sorting a {
+ color: #555;
+ font-weight: normal;
+ text-decoration: none
+}
+
+.comment-sorting a:hover {
+ color: #999
+}
+
+.comment {
+ padding: 5px;
+ margin-bottom: 15px
+}
+
+.comment-title {
+ border-bottom: 1px dotted #eee;
+ margin-left: 55px
+}
+
+.comment-date {
+ color: #999;
+ font-weight: 200
+}
+
+.comment-actions {
+ text-align: right
+}
+
+.comment-content {
+ margin-left: 55px
+}
+
+.comments .text-editor textarea {
+ height: 90px
+}
+
+.comments .text-editor .text-editor-preview-area {
+ height: 90px
+}
+
+.comments .comment-highlighted {
+ background-color: #f6f8fa;
+ border: 2px solid #ffeb8e
+}
+
+.comments .comment-highlighted:hover {
+ background-color: #f6f8fa
+}
+
+.comments .comment:hover {
+ background: #f6f8fa
+}
+
+.comments .comment:nth-child(even):not(.comment-highlighted) {
+ background: #f6f8fa
+}
+
+.comments .comment:nth-child(even):not(.comment-highlighted):hover {
+ background: #f6f8fa
+}
+
+.subtask-cell {
+ padding: 4px 10px;
+ border-top: 1px dotted #dedede;
+ border-left: 1px dotted #dedede;
+ display: table-cell;
+ vertical-align: middle
+}
+
+.subtask-cell a {
+ color: #333;
+ text-decoration: none
+}
+
+.subtask-cell a:hover,
+.subtask-cell a:focus {
+ color: #0366d6
+}
+
+.subtask-cell:first-child {
+ border-left: none
+}
+
+@media (max-width: 768px) {
+ .subtask-cell {
+ width: 90%;
+ display: block;
+ border-left: none
+ }
+}
+
+.task-list-subtasks {
+ display: table;
+ width: 100%
+}
+
+@media (max-width: 768px) {
+ .task-list-subtasks {
+ display: block
+ }
+}
+
+.task-list-subtask {
+ display: table-row
+}
+
+@media (max-width: 768px) {
+ .task-list-subtask {
+ display: block
+ }
+}
+
+@media (max-width: 768px) {
+ .subtask-assignee,
+ .subtask-time-tracking-cell {
+ display: none
+ }
+}
+
+.task-links-table td {
+ vertical-align: middle
+}
+
+.task-links-task-count {
+ color: #999;
+ font-weight: normal
+}
+
+.task-link-closed {
+ text-decoration: line-through
+}
+
+.text-editor {
+ margin-top: 10px
+}
+
+.text-editor a {
+ font-size: 1em;
+ color: #999;
+ text-decoration: none;
+ margin-right: 10px
+}
+
+.text-editor a:hover {
+ color: #0366d6
+}
+
+.text-editor .text-editor-preview-area {
+ border: none;
+ width: 700px;
+ max-width: 99%;
+ height: 250px;
+ overflow: auto;
+ padding: 2px
+}
+
+.text-editor textarea {
+ width: 700px;
+ max-width: 98%;
+ height: 250px;
+ padding: 10px;
+}
+
+.markdown {
+ line-height: 1.4em
+}
+
+.markdown h1 {
+ margin-top: 5px;
+ margin-bottom: 10px;
+ font-weight: bold
+}
+
+.markdown h2 {
+ font-weight: bold
+}
+
+.markdown p {
+ margin-bottom: 10px
+}
+
+.markdown ol,
+.markdown ul {
+ margin-left: 25px;
+ margin-top: 10px;
+ margin-bottom: 10px
+}
+
+.markdown pre {
+ background: #3333330d;
+ padding: 10px;
+ border-radius: 5px;
+ border: 1px solid #ddd;
+ overflow: auto;
+ overflow-wrap: initial;
+ color: #555
+}
+
+.markdown blockquote {
+ font-style: italic;
+ border-left: 3px solid #ddd;
+ padding-left: 10px;
+ margin-bottom: 10px;
+ margin-left: 20px
+}
+
+.markdown img {
+ display: block;
+ max-width: 80%;
+ margin-top: 10px
+}
+
+.documentation {
+ margin: 0 auto;
+ padding: 20px;
+ max-width: 850px;
+ background: #fefefe;
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ color: #555
+}
+
+.documentation img {
+ border: 1px solid #333
+}
+
+.documentation h1 {
+ text-decoration: none;
+ margin-bottom: 30px
+}
+
+.documentation h2 {
+ text-decoration: none;
+ border-bottom: 1px solid #ccc;
+ margin-bottom: 25px
+}
+
+.documentation li {
+ line-height: 30px
+}
+
+.panel {
+ border-radius: 4px;
+ padding: 8px 35px 8px 10px;
+ margin-top: 10px;
+ margin-bottom: 15px;
+ border: 1px solid #ddd;
+ color: #333;
+ background-color: #fcfcfc;
+ overflow: auto
+}
+
+.panel li {
+ list-style-type: square;
+ margin-left: 20px;
+ line-height: 1.35em
+}
+
+.activity-event {
+ margin-bottom: 15px;
+ padding: 10px
+}
+
+.activity-event:nth-child(even) {
+ background: #fafafa
+}
+
+.activity-event:hover {
+ background: #f6f8fa
+}
+
+.activity-date {
+ margin-left: 10px;
+ font-weight: normal;
+ color: #999
+}
+
+.activity-content {
+ margin-left: 55px
+}
+
+.activity-title {
+ font-weight: bold;
+ color: #000;
+ border-bottom: 1px dotted #efefef
+}
+
+.activity-description {
+ color: #555;
+ margin-top: 10px
+}
+
+@media (max-width: 480px) {
+ .activity-description {
+ overflow: auto
+ }
+}
+
+.activity-description li {
+ list-style-type: circle
+}
+
+.activity-description ul {
+ margin-top: 10px;
+ margin-left: 20px
+}
+
+.user-mention-link {
+ font-weight: bold;
+ color: #000;
+ text-decoration: none
+}
+
+.user-mention-link:hover {
+ color: #555
+}
+
+.image-slideshow-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.95);
+ overflow: auto;
+ z-index: 100
+}
+
+.image-slideshow-overlay img {
+ display: block;
+ margin: auto
+}
+
+.image-slideshow-overlay figcaption {
+ color: #fff;
+ opacity: 0.7;
+ position: absolute;
+ bottom: 5px;
+ right: 15px
+}
+
+.slideshow-icon {
+ color: #fff;
+ position: absolute;
+ font-size: 2.5em;
+ opacity: 0.6
+}
+
+.slideshow-icon:hover {
+ opacity: 0.9;
+ cursor: pointer
+}
+
+.slideshow-previous-icon {
+ left: 10px;
+ top: 45%
+}
+
+.slideshow-next-icon {
+ right: 10px;
+ top: 45%
+}
+
+.slideshow-close-icon {
+ right: 10px;
+ top: 10px;
+ font-size: 1.4em
+}
+
+.slideshow-download-icon {
+ left: 10px;
+ bottom: 10px;
+ font-size: 1.3em
+}
+
+.list-item-links,
+.list-item-actions {
+ display: inline-block;
+ float: left;
+ margin-left: 10px
+}
+
+.list-item-links a {
+ margin: 0
+}
+
+.list-item-action-hidden {
+ display: none
+}
+
+.bulk-change-checkbox {
+ float: left
+}
+
+.bulk-change-inputs {
+ float: left;
+ padding-left: 10px
+}
+
+.bulk-change-inputs label {
+ margin-top: 0;
+ margin-bottom: 3px
+}
+
+#to-top {
+ margin: 0 0px 30px 0px;
+}
+
+#backToTop {
+ position: fixed;
+ left: 50%;
+ bottom: -25px;
+ display: block;
+ width: 50px;
+ height: 50px;
+ z-index: 10000;
+ transition: all 0.4s;
+ -webkit-transition: all 0.4s;
+ -moz-transition: all 0.4s;
+ -o-transition: all 0.4s;
+ -ms-transition: all 0.4s;
+}
+
+#backToTop.topshow {
+ bottom: 0;
+}
+
+#backToTop span {
+ position: absolute;
+ left: -25px;
+ bottom: -25px;
+ display: block;
+ width: 50px;
+ height: 50px;
+ background: rgba(50, 50, 50, 0.15);
+ text-decoration: none;
+ -webkit-border-radius: 35px;
+ -moz-border-radius: 35px;
+ border-radius: 35px;
+ transition: all 0.4s;
+ -webkit-transition: all 0.4s;
+ -moz-transition: all 0.4s;
+ -o-transition: all 0.4s;
+ -ms-transition: all 0.4s;
+}
+
+#backToTop span:hover {
+ bottom: 5px;
+ background: rgba(70, 70, 70, 0.9);
+}
+
+#backToTop i:before {
+ position: relative;
+ left: 12px;
+ top: 0;
+ display: block;
+ margin: 0;
+ width: 50px;
+ color: #fff;
+ font-size: 27px;
+ -webkit-transition: all 0.6s ease;
+ -moz-transition: all 0.6s ease;
+ -ms-transition: all 0.6s ease;
+ -o-transition: all 0.6s ease;
+ transition: all 0.6s ease;
+}
+
+#backToTop:hover i:before {
+ top: 9px;
+}
diff --git a/plugins/Customizer/Assets/css/themes/KindaDark.css b/plugins/Customizer/Assets/css/themes/KindaDark.css
new file mode 100644
index 00000000..542c69f8
--- /dev/null
+++ b/plugins/Customizer/Assets/css/themes/KindaDark.css
@@ -0,0 +1,685 @@
+/*
+ * Modified version of "Breathe" Theme for Kanboard
+ * Licensed under the MIT license - Oxygen/LICENSE
+ * - https://github.com/kenlog/Oxygen
+ * Copyright (c) 2019 Samm Du [https://sammdu.com]
+ */
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+}
+
+input,
+textarea {
+ background-color: #fff;
+ color: #000;
+}
+
+::-webkit-input-placeholder {
+ /* Chrome/Opera/Safari */
+ color: #3c3c3c;
+}
+
+::-moz-placeholder {
+ /* Firefox 19+ */
+ color: #3c3c3c;
+}
+
+:-ms-input-placeholder {
+ /* IE 10+ */
+ color: #3c3c3c;
+}
+
+:-moz-placeholder {
+ /* Firefox 18- */
+ color: #3c3c3c;
+}
+
+::placeholder {
+ color: #3c3c3c;
+}
+
+h1 {
+ color: #fff;
+}
+
+a {
+ color: #2c52e3;
+}
+
+.js-modal-medium {
+ color: #2c52e3;
+}
+
+.js-modal-large {
+}
+
+.page-header ul li .fa {
+ color: #2c52e3;
+}
+
+.page-header ul li a {
+ text-decoration: none;
+}
+
+#modal-overlay .fa {
+ color: #222427;
+}
+
+a .fa {
+ color: #fff;
+}
+
+.project-header * {
+ border-radius: 0 !important;
+}
+
+.project-header li .fa {
+ color: #222427;
+}
+
+.table-list-row .fa {
+ color: #222427;
+}
+
+.dropdown-submenu-open .fa {
+ color: #222427;
+}
+
+.project-header .action-menu .fa {
+ color: #222427;
+}
+
+.text-editor .fa {
+ color: #222427;
+}
+
+.sidebar .fa {
+ color: #222427;
+}
+
+.board-column-title .fa {
+ color: #222427;
+}
+
+.task-board a {
+ color: #222427;
+ text-decoration: none;
+}
+
+.sidebar>ul a:hover {
+ color: #2c52e3;
+}
+
+.logo>a>img {
+ vertical-align: middle;
+ padding: 0.5em 1em 0.5em 0.5em;
+}
+
+div.ganttview-vtheader-series-name {
+ padding: 0 6px;
+}
+
+th,
+td {
+ padding: 10px;
+}
+
+header {
+ border-bottom: none;
+ box-shadow: 0px 1px 3px 0 rgba(46, 61, 73, .12);
+ padding: 1em 0.6em;
+ margin-bottom: 1em;
+ background-color: #222427 !important;
+}
+
+.header img {
+ float: left;
+}
+
+.header h2 {
+ position: relative;
+ color: #2c52e3;
+ top: 13px;
+ left: 10px;
+ margin: 0;
+}
+
+header .title-container {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+
+label {
+ font-weight: bold;
+ margin-top: 18px;
+}
+
+.table-list-header {
+ background: #222427;
+ border-radius: 0;
+ border-color: #222427;
+ line-height: 28px;
+ padding: 0.2em;
+}
+
+.table-list-header .table-list-header-count {
+ color: #fff;
+ display: inline-block;
+ float: left;
+ margin-left: 0.4em;
+}
+
+.table-list-header a {
+ color: #fff;
+ font-weight: 500;
+}
+
+.table-list-header a:hover {
+ color: #fff;
+ font-weight: 500;
+}
+
+.table-list-row:last-child {
+ border-radius: 0;
+}
+
+.task-board {
+ margin-bottom: 8px;
+ padding: 12px;
+ border-radius: 0;
+ border-top: none;
+ border-right: none;
+ border-left: none;
+ border-bottom: 0.4em solid rgba(0, 0, 0, 0.3);
+}
+
+div.task-board-recent {
+ border-width: 0.4em;
+}
+
+.task-board-title {
+ font-size: 1.25em;
+ font-weight: 600;
+}
+
+.task-board-header i {
+ color: #222427 !important;
+}
+
+.task-show-details {
+ border-radius: 0;
+ margin-bottom: 20px;
+}
+
+.task-show-details h2 {
+ background-color: rgba(0, 0, 0, 0.3);
+ padding: 20px;
+ border-radius: 0;
+ color: #fff;
+}
+
+.task-show-details ul {
+ padding: 20px;
+}
+
+.task-summary-container {
+ border: none;
+ border-radius: 0;
+ border-bottom: 5px solid;
+ padding: 20px;
+}
+
+.table-small {
+ font-size: 1em;
+}
+
+table th {
+ text-align: left;
+ padding: 0.8em 3px 0.6em 3px;
+ border: 1px solid #eee;
+ background: #fbfbfb;
+}
+
+table td {
+ border: none;
+ padding: 0.5em 7px;
+ vertical-align: top;
+}
+
+.sidebar ul {
+ padding-bottom: 20px;
+ border-bottom: 1px solid #ddd;
+ border-radius: 0;
+}
+
+.sidebar ul:last-of-type {
+ border-bottom: none;
+}
+
+.sidebar-collapse i,
+.sidebar-collapsed .sidebar {
+ background-color: #999;
+ border-radius: 0;
+ padding: 5px 10px 5px 8px;
+}
+
+.sidebar-collapse i,
+.sidebar-collapsed .sidebar i {
+ color: #fff;
+}
+
+.sidebar-collapse i:hover,
+.sidebar-collapsed .sidebar:hover {
+ background-color: #ccc;
+}
+
+.page-header {
+ margin: 15px 0;
+ background-color: #eee;
+ padding: 6px 10px 10px 10px;
+ border-bottom: 1px solid #ddd;
+ border-top: 1px solid #ddd;
+}
+
+.page-header li {
+ font-size: 1.1em;
+}
+
+.page-header h2 {
+ border-bottom: none;
+}
+
+header .avatar img {
+ width: 48px;
+ border-radius: 0;
+ margin-left: 0.5em;
+ margin-right: 0.4em;
+}
+
+.menus-container .avatar+i {
+ display: none;
+}
+
+.listing {
+ border: none;
+}
+
+.fa-play {
+ font-size: 1.2em;
+ background-color: #ddd;
+ border-radius: 100%;
+ width: 25px;
+ height: 28px;
+ padding: 8px 0 0 15px;
+ margin-right: 8px;
+}
+
+.fa-play:hover {
+ background-color: #999;
+ color: #fff;
+}
+
+.fc-event .fc-content {
+ padding: 4px 8px;
+}
+
+.board-add-icon {
+ float: left;
+ padding: 0 5px;
+}
+
+.board-add-icon i {
+ text-decoration: none;
+ color: #289E7B;
+ font-size: 1.4em;
+}
+
+.board-add-icon i:focus,
+.board-add-icon i:hover {
+ text-decoration: none;
+ color: #23292d;
+}
+
+.form-column select {
+ font-size: 1.2em;
+ margin: 10px 0 2px 0;
+}
+
+.form-column:nth-of-type(2) {
+ padding: 10px 25px;
+ background-color: #f7f7f7;
+}
+
+.form-actions {
+ font-size: 1.2em;
+}
+
+.page-header ul.dropdown-submenu-open {
+ margin: 10px 0 0 -10px;
+ padding: 0;
+}
+
+.select-dropdown-input-container {
+ position: relative;
+ border: 2px solid #3c3c3c;
+ width: 20em;
+ max-width: 20em;
+ border-radius: 0;
+ background-color: #222427;
+ margin-right: 1em;
+}
+
+header input {
+ color: #fff !important;
+}
+
+.board-selector-container input,
+.board-selector-container textarea {
+ background-color: inherit;
+ color: #fff;
+}
+
+.select-dropdown-input-container .select-dropdown-chevron {
+ position: relative;
+ color: #fff;
+ top: auto;
+ right: -5%;
+}
+
+ul.dropdown-submenu-open {
+ margin: 10px 0 0 -10px;
+ padding: 0;
+ background-color: #fff;
+ border: 1px solid #222427;
+ border-radius: 0;
+ box-shadow: 0 0.1em 0.5em rgba(0, 0, 0, 0.5);
+}
+
+.dropdown-submenu-open .no-hover {
+ cursor: default;
+ padding: 1em;
+ font-size: 1em;
+ background-color: #222427;
+ color: #fff;
+}
+
+.dropdown-submenu-open li {
+ padding: 6px 10px;
+}
+
+#select-dropdown-menu {
+ background-color: #222427;
+ border-color: #222427;
+}
+
+.select-dropdown-menu-item.active {
+ color: #fff;
+ background: #2c52e3;
+}
+
+.views li {
+ padding: 0.4em;
+}
+
+.filters {
+ border: none;
+}
+
+.filters ul.dropdown-submenu-open {
+ margin: 6px 0 0 8px;
+}
+
+.markdown pre {
+ background: #3333330d;
+ max-height: 600px;
+ overflow: auto;
+ margin-bottom: 1em;
+ padding: 5px;
+ color: #242729;
+ font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, sans-serif;
+}
+
+a i.web-notification-icon {
+ color: #D45353;
+}
+
+.fa-play {
+ font-size: 1.2em;
+ background-color: #ddd;
+ border-radius: 100%;
+ width: 25px;
+ height: 25px !important;
+ padding: 9px 3px 0 9px !important;
+ margin-right: 8px;
+}
+
+#modal-box {
+ padding: 10px;
+}
+
+.table-list-category {
+ padding: 2px 2px 2px 5px;
+}
+
+.table-list-row {
+ transition: 0.15s;
+}
+
+.table-list-row:hover {
+ background: #f5f5f5;
+ border: none;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
+}
+
+.page-header {
+ margin: 15px 0;
+ background-color: #ffffff;
+ padding: 6px 10px 10px 10px;
+ border-bottom: 1px solid #f7f7f7;
+ border-top: 1px solid #f7f7f7;
+}
+
+.comments .comment-highlighted {
+ background-color: #fff;
+ border: 2px solid #F5E982;
+ border-radius: 0;
+}
+
+.comments .comment:hover {
+ background: #f5f5f5;
+ border-radius: 0;
+}
+
+.comments .comment:nth-child(even):not(.comment-highlighted):hover {
+ border-radius: 0;
+ background: #f5f5f5;
+}
+
+.comments .comment:nth-child(even):not(.comment-highlighted) {
+ background: #fbfbfb;
+ border-radius: 0;
+}
+
+.sidebar {
+ background-color: #f7f7f7;
+ padding: 7px;
+ border-radius: 0;
+ border-left: 2px solid #e9e9e9;
+ box-shadow: 1px 0px 7px 0 rgba(46, 61, 73, .12);
+}
+
+.input-addon * {
+ border-radius: 0 !important;
+}
+
+input[type="number"]:focus,
+input[type="date"]:focus,
+input[type="email"]:focus,
+input[type="password"]:focus,
+input[type="text"]:focus {
+ color: #000;
+ border-color: #2c52e3;
+ outline: 0;
+ box-shadow: 0 0 0 0.2rem rgba(222, 222, 222, 0.25);
+}
+
+textarea:focus {
+ color: #000;
+ border-color: #2c52e3;
+ outline: 0;
+ box-shadow: 0 0 0 0.2rem rgba(222, 222, 222, 0.25);
+}
+
+input[type="number"],
+input[type="date"],
+input[type="email"],
+input[type="password"],
+input[type="text"]:not(.input-addon-field) {
+ padding: 0.4em;
+ transition: box-shadow 1s;
+}
+
+.select-dropdown-input-container input.select-dropdown-input {
+ width: 80%;
+}
+
+.text-editor textarea {
+ padding: 3px;
+ transition: box-shadow 1s;
+}
+
+.table-list-row {
+ padding: 0.4em;
+}
+
+select {
+ border: 1px solid #ccc;
+ background: #f9f9f9;
+ padding: 3px;
+}
+
+.btn-blue {
+ border-color: #2c52e3;
+ background-color: #2c52e3;
+ color: #fff;
+}
+
+.btn-blue:hover,
+.btn-blue:focus {
+ border-color: #2c52e3;
+ background: #ffffff;
+ color: #2c52e3;
+}
+
+.select2-container--default.select2-container--focus .select2-selection--multiple {
+ border: solid #2c52e3 1px;
+ outline: 0;
+}
+
+.select2-container--default .select2-results__option--highlighted[aria-selected] {
+ background-color: #2c52e3;
+ color: #fff;
+}
+
+.board-column-header-task-count {
+ display: none;
+}
+
+.board-add-icon i:focus,
+.board-add-icon i:hover {
+ text-decoration: none;
+ color: #91C259;
+}
+
+.board-column-title *:hover {
+ color: #aaa;
+}
+/*
+.board-column-title *:hover {
+ color: #aaa;
+} */
+
+.board-add-icon i {
+ text-decoration: none;
+ color: #2c52e3;
+ font-size: 1.4em;
+}
+
+.dropdown-submenu-open li:not(.no-hover):hover {
+ background: #2c52e3;
+ color: #fff;
+}
+
+.sidebar>ul li.active a {
+ color: #2c52e3;
+ font-weight: bold;
+}
+
+.table-list-row .table-list-title a:hover,
+.table-list-row .table-list-title a:focus {
+ text-decoration: underline;
+ color: #2c52e3;
+}
+
+.image-slideshow-overlay img {
+ display: block;
+ margin: auto;
+ max-width: 100%;
+}
+
+.input-addon-item {
+ background-color: #222427;
+ color: #fff;
+}
+
+span.task-date-overdue {
+ opacity: 1.0;
+ color: #e90529;
+}
+
+.board-add-icon i:focus,
+.board-add-icon i:hover {
+ text-decoration: none;
+ color: #2BBF5F;
+}
+
+span.task-date-today {
+ opacity: 1.0;
+ color: #2c52e3;
+}
+
+#task-summary h2 {
+ color: #222427;
+}
+
+.input-addon {
+ position: relative;
+ top: -0.07em;
+}
+
+.input-addon-field, .input-addon-item {
+ padding: 0.25em 0.75em;
+}
+
+#modal-box {
+ border-radius: 0;
+}
+
+.selection * {
+ border-radius: 0 !important;
+}
+
+.task-date {
+ font-size: 1.5em;
+ color: #000;
+}
+
+.task-board-icons-row {
+ font-size: 1.2em;
+}
+
+.sidebar > ul a {
+ color: #222427;
+}
diff --git a/plugins/Customizer/Assets/css/themes/Material.css b/plugins/Customizer/Assets/css/themes/Material.css
new file mode 100644
index 00000000..706afaf0
--- /dev/null
+++ b/plugins/Customizer/Assets/css/themes/Material.css
@@ -0,0 +1,1427 @@
+/*!
+ * Material Like - Theme for Kanboard
+ * Licensed under the MIT license - kanboard-theme-material-like/LICENSE
+ * https://github.com/erichk4/kanboard-theme-material-like
+ * Copyright (c) 2017 Erich Munz
+ */
+
+@import "https://fonts.googleapis.com/css?family=Open+Sans";
+
+/* CSSTidy 1.5.2: Mon, 25 Sep 2017 09:04:23 -0700 */
+body
+{
+ font-family:'Open Sans',sans-serif;
+ font-size:14px;
+ background-color:#f1f1f1!important
+}
+
+div#modal-box {
+ width: max-content!important;
+}
+
+.logo a {
+ opacity: 1;
+ color: inherit;
+ text-decoration: none;
+}
+
+.title-container .logo {
+ display: block;
+ margin-bottom: 4px;
+}
+
+.title-container .title:before {
+ font-family: FontAwesome;
+ content: "\f054";
+ display: inline-block;
+ padding-right: 2px;
+ vertical-align: middle;
+ font-size: 0.6em;
+}
+
+#task-summary h2:before {
+ font-family: FontAwesome;
+ content: "\f017";
+ display: inline-block;
+ padding-right: 8px;
+ vertical-align: middle;
+ font-size: 0.6em;
+}
+
+@media only screen and (max-width: 1024px) {
+ body
+ {
+ font-size:12px
+ }
+}
+
+.ui-widget-shadow {
+ -webkit-box-shadow: 0 0 5px #ccc;
+ box-shadow: 0 0 5px #ccc;
+}
+
+.ui-widget.ui-widget-content {
+ border: 1px solid #ccc;
+}
+
+.sidebar-content .task-list-avatars .avatar
+{
+ border:none;
+ background:none;
+ padding:0;
+ overflow:initial;
+ margin:0;
+ margin-right:3px;
+ margin-top:5px
+}
+
+tr td:last-child {
+ border-right:1px solid #e5e5e5;
+}
+
+tr:last-child td {
+ border-bottom:1px solid #e5e5e5;
+}
+
+.sidebar-content .accordion-section
+{
+ box-shadow:0 1px 1px rgba(0,0,0,.04);
+ background-color:#fff;
+ border:1px solid #e5e5e5;
+ padding:12px
+}
+
+.accordion-section.accordion-collapsed .accordion-title
+{
+ border-bottom:none
+}
+
+.project-overview-columns
+{
+ box-shadow:0 1px 1px rgba(0,0,0,.04);
+ background-color:#fff;
+ border:1px solid #e5e5e5;
+ padding:12px
+}
+
+.accordion-section
+{
+ box-shadow:0 1px 1px rgba(0,0,0,.04);
+ background-color:#fff;
+ border:1px solid #e5e5e5;
+ padding:12px;
+ margin-bottom:25px
+}
+
+.sidebar-content .accordion-title h3
+{
+ margin-top:12px;
+ background-color:none;
+ border:none;
+ box-shadow:none;
+ position:relative;
+ padding:12px;
+ padding-left:0;
+ margin-bottom:-11px
+}
+
+.sidebar-content .filter-box
+{
+ float:none;
+ border:1px solid #e5e5e5;
+ box-shadow:0 1px 1px rgba(0,0,0,.04);
+ max-width:inherit;
+ margin-top:12px
+}
+
+.sidebar-content .filter-box form
+{
+ margin-top:0
+}
+
+.filter-box form
+{
+ border:none;
+ box-shadow:none
+}
+
+.js-chart-task-time-column-rendered
+{
+ background:#fff;
+ margin-top:10px;
+ padding:20px 0;
+ margin-bottom:14px;
+ border:1px solid #e5e5e5;
+ box-shadow:0 1px 1px rgba(0,0,0,.04)
+}
+
+.panel li
+{
+ list-style-type:square;
+ margin-left:20px;
+ line-height:1.8em
+}
+
+.sidebar-content .activity-event .avatar
+{
+ border-radius:0;
+ padding:8px 35px 8px 10px;
+ margin-top:10px;
+ margin-bottom:15px;
+ border:none;
+ color:#333;
+ background-color:transparent;
+ overflow:auto;
+ box-shadow:none
+}
+
+.project-header
+{
+ background:#fff;
+ padding:0;
+ margin-top:10px;
+ margin-bottom:10px
+}
+
+.task-board-title
+{
+ margin-top:5px;
+ margin-bottom:20px
+}
+
+#modal-overlay
+{
+ position:fixed;
+ top:0;
+ left:0;
+ width:100%;
+ height:100%;
+ background:rgba(0,0,0,0.36);
+ overflow:auto;
+ z-index:100
+}
+
+#modal-box .page-header
+{
+ box-shadow:none;
+ background:#fff;
+ padding:10px 20px 10px 0;
+ border:none;
+ border-bottom:1px solid #e5e5e5;
+ margin-top:0;
+ margin-bottom:10px
+}
+
+.task-form-secondary-column
+{
+ max-width:250px;
+ min-width:200px;
+ max-height:600px;
+ overflow:auto;
+ width:20%;
+ background:#f2f2f2;
+ padding:20px
+}
+
+#modal-box form
+{
+ margin-top:10px;
+ background-color:#fff;
+ border:none;
+ box-shadow:none;
+ position:relative;
+ padding-left:0
+}
+
+.table-list-row:hover
+{
+ background: rgba(51, 146, 204, 0.1);
+ border-bottom:none;
+ border-right:none
+}
+
+.table-list-row.table-border-left
+{
+ border-left:none
+}
+
+.filter-box
+{
+ float:right
+}
+
+.project-header .views-switcher-component
+{
+ margin-top:16px;
+ float:left
+}
+
+.project-header .dropdown-component
+{
+ margin-top:16px;
+ margin-right:5px;
+ float:left
+}
+
+.sidebar-title
+{
+ padding:8px
+}
+
+input[type="number"],input[type="date"],input[type="email"],input[type="password"],input[type="text"]:not(.input-addon-field)
+{
+ color:#999;
+ border:2px solid #ccc;
+ max-width:88.5%;
+ font-size:1em;
+ height:25px;
+ padding:8px;
+ font-family:inherit
+}
+
+.select-dropdown-input-container input[type="text"]
+{
+ border:none
+}
+
+.select-dropdown-input-container
+{
+ position:relative;
+ border:2px solid #ccc;
+ border-radius:0
+}
+
+.select-dropdown-input-container .select-dropdown-chevron
+{
+ color:#555;
+ position:absolute;
+ top:12px;
+ right:8px;
+ cursor:pointer
+}
+
+textarea
+{
+ max-width:99%;
+ height:200px;
+ font-size:1em;
+ color:#999;
+ border:2px solid #ccc;
+ width:300px;
+ padding:8px;
+ font-family:inherit
+}
+
+select
+{
+ padding:8px;
+ max-width:95%;
+ width:200px;
+ border:#ccc 2px solid
+}
+
+.sidebar>ul li.active a
+{
+ color:#3392cc;
+ font-weight:700
+}
+
+.sidebar>ul li.active
+{
+ border-left:5px solid #3392cc;
+ padding-left:8px
+}
+
+.page-header
+{
+ margin-top:14px;
+ margin-bottom:0
+}
+
+.btn
+{
+ padding:10px 16px
+}
+
+.page-header,header
+{
+ border:1px solid #e5e5e5;
+ box-shadow:0 1px 1px rgba(0,0,0,.04);
+ background:#fff;
+ padding:20px
+}
+
+header {
+ box-sizing: border-box;
+ display: flex;
+ flex-wrap: wrap;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ border-bottom: 1px solid #dedede;
+}
+
+header .menus-container
+{
+ width:10%;
+ margin-top:12px
+}
+
+header .title-container
+{
+ width:65%;
+ margin-top:10px
+}
+
+.sidebar-content fieldset
+{
+ margin-top:10px;
+ margin-bottom:10px;
+ position:relative
+}
+
+.sidebar-content .panel,.sidebar-content fieldset
+{
+ border:none;
+ border-bottom:1px solid #efefef
+}
+
+.sidebar-content .avatar,.sidebar-content .panel
+{
+ border-radius:0;
+ padding:8px 35px 8px 10px;
+ margin-top:10px;
+ margin-bottom:15px;
+ border:1px solid #e5e5e5;
+ color:#333;
+ background-color:#fff;
+ overflow:auto;
+ box-shadow:0 1px 1px rgba(0,0,0,.04)
+}
+
+.table-list
+{
+ box-shadow:0 1px 1px rgba(0,0,0,.04);
+ background-color:#fff;
+ border:1px solid #e5e5e5
+}
+
+.table-list-header,.table-list-row
+{
+ border:none
+}
+
+.sidebar
+{
+ max-width:240px;
+ min-width:190px;
+ width:18%;
+ border:1px solid #e5e5e5;
+ box-shadow:0 1px 1px rgba(0,0,0,.04);
+ background-color:#fff;
+ margin-top:12px
+}
+
+label
+{
+ cursor:pointer;
+ display:block;
+ margin-top:10px;
+ margin-bottom:4px;
+ font-weight:700
+}
+
+.sidebar-content legend
+{
+ top:14px;
+ left:0;
+ right:0;
+ padding-bottom:7px;
+ font-size:1.2em
+}
+
+.sidebar-icons>ul li
+{
+ padding-left:12px
+}
+
+.sidebar-icons>ul li:hover,.sidebar-icons>ul li.active
+{
+ padding-left:12px;
+ border-left:none
+}
+
+.sidebar-content h3,form h3
+{
+ margin-top:12px;
+ background-color:#fff;
+ border:1px solid #e5e5e5;
+ box-shadow:0 1px 1px rgba(0,0,0,.04);
+ position:relative;
+ padding:12px;
+ margin-bottom:-11px
+}
+
+#task-summary h2
+{
+ margin-top:12px;
+ background-color:#fff;
+ border:1px solid #e5e5e5;
+ box-shadow:0 1px 1px rgba(0,0,0,.04);
+ position:relative;
+ padding:12px
+}
+
+.activity-date,.activity-description,.btn,.buttons-header,.documentation,.dropdown-submenu-open li,.fc-event,.form-help,.menu-inline li,.page-header li,.project-overview-column span,.project-overview-columns,.sidebar,.subtasks-table,.table-list,.table-list-row .table-list-icons,.table-small,.task-board,.task-board-category,.task-board-title,.task-summary-buttons,.task-summary-column,.textarea-dropdown li,.tooltip .fa-info-circle,.views,div.ganttview-vtheader-series-name,header ul,legend,table
+{
+ font-size:1em
+}
+
+.ui-widget
+{
+ font-family:'Open Sans',sans-serif;
+ font-size:1em
+}
+
+#ui-datepicker-div,.ui-datepicker table,div.ui-tooltip
+{
+ font-size:1em
+}
+
+.assign-me,.comment-actions,.comment-date,.dashboard-project-stats span
+{
+ font-size:.85em
+}
+
+input[type=date],input[type=email],input[type=number],input[type=password],input[type=text]
+{
+ font-family:'Open Sans',sans-serif;
+ font-size:14px
+}
+
+input[type=file]
+{
+ font-family:'Open Sans',sans-serif;
+ font-size:14px
+}
+
+select
+{
+ font-family:'Open Sans',sans-serif;
+ font-size:14px
+}
+
+.filter-box input[type=text]
+{
+ font-size:14px
+}
+
+.chosen-container
+{
+ font-size:14px
+}
+
+.CodeMirror,.editor-toolbar,.filter-box div.dropdown:last-child,.filter-box input[type=text],.input-addon-item:last-child,.panel,.table-list-header,.table-list-row:last-child,.views li:first-child,.views li:last-child
+{
+ border-bottom-left-radius:0;
+ border-top-left-radius:0;
+ border-bottom-right-radius:0;
+ border-top-right-radius:0
+}
+
+#modal-box,.alert,.btn,.documentation,.listing,.markdown pre,.task-board,.task-show-title,.task-summary-container,ul.dropdown-submenu-open
+{
+ border-radius:0
+}
+
+.ui-corner-all,.ui-corner-bottom,.ui-corner-br,.ui-corner-right
+{
+ border-bottom-right-radius:0;
+ border-bottom-left-radius:0;
+ border-top-right-radius:0;
+ border-top-left-radius:0
+}
+
+.select2-container--default .select2-selection--multiple,.select2-container--default .select2-selection--single
+{
+ border-radius:0
+}
+
+#select-dropdown-menu,.select-dropdown-input-container
+{
+ border-radius:0
+}
+
+#select-dropdown-menu
+{
+ border-top:none
+}
+
+.chosen-container .chosen-results li.highlighted
+{
+ background-color:#36c;
+ background-image:none
+}
+
+.chosen-container-single .chosen-single
+{
+ -webkit-box-shadow:none;
+ box-shadow:none;
+ background:#fff
+}
+
+.chosen-container-active.chosen-with-drop .chosen-single
+{
+ background:#fff
+}
+
+.color-picker-square.color-yellow,.task-board.color-yellow,.task-summary-container.color-yellow
+{
+ background-color:#ffeb8e!important;
+ border-color:#e4c600!important
+}
+
+.alert
+{
+ background-color:#f7e400;
+ border-color:#e8d700;
+ color:#776e00
+}
+
+a
+{
+ color:#3392cc
+}
+
+a:hover
+{
+ color:#3392cc
+}
+
+a:focus
+{
+ color:#3392cc
+}
+
+h2
+{
+ font-size:1.3em;
+ font-weight:700
+}
+
+h3
+{
+ font-size:1.3em;
+ font-weight:700
+}
+
+legend
+{
+ font-weight:700
+}
+
+input[type=date],input[type=email],input[type=number],input[type=password],input[type=text]
+{
+ padding-left:5px;
+ border-color:#ccc;
+ color:#333
+}
+
+input[type=date]:focus,input[type=email]:focus,input[type=number]:focus,input[type=password]:focus,input[type=text]:focus
+{
+ -webkit-box-shadow:none;
+ box-shadow:none
+}
+
+input[type=text]:not(.input-addon-field)
+{
+ color:#333
+}
+
+textarea
+{
+ padding:5px
+}
+
+textarea:focus
+{
+ -webkit-box-shadow:none;
+ box-shadow:none
+}
+
+.select2-container--default,.select2-selection--single
+{
+ min-width:200px;
+ border-color:#ccc
+}
+
+.select2-container--default .select2-selection__rendered,.select2-selection--single .select2-selection__rendered
+{
+ color:#333
+}
+
+.select2-container--default .select2-selection--multiple,.select2-selection--single .select2-selection--multiple
+{
+ border-color:#ccc;
+ border-width:2px
+}
+
+.select2-container--default .select2-selection--single
+{
+ background-color:#fff;
+ border:2px solid #ccc;
+ border-radius:0
+}
+
+.color-picker-option
+{
+ height:20px
+}
+
+#select-dropdown-menu
+{
+ overflow:auto
+}
+
+table select
+{
+ margin-top:0
+}
+
+.filter-box input[type=text]
+{
+ padding-left:5px
+}
+
+.form-actions
+{
+ margin-bottom:4px
+}
+
+.form-column ul
+{
+ margin-top:5px
+}
+
+.form-help
+{
+ margin-top:5px;
+ font-size:.85em;
+ color:gray;
+ font-style:italic
+}
+
+.form-required
+{
+ color:#900
+}
+
+.buttons-header
+{
+ font-size:.8em;
+ margin-top:10px;
+ margin-bottom:15px
+}
+
+.btn
+{
+ font-size:1.2em;
+ font-weight:400;
+ cursor:pointer;
+ display:inline-block;
+ border-radius:2px;
+ padding:8px 18px;
+ margin:0;
+ border:1px solid #ccc;
+ background:#fff;
+ color:#000
+}
+
+.btn-blue
+{
+ background:#3392cc;
+ border:none;
+ color:#fff
+}
+
+.btn-blue:focus,.btn-blue:hover
+{
+ background:#036;
+ border:none
+}
+
+.btn-red
+{
+ background:#900;
+ border:none;
+ color: #fff;
+}
+
+.btn-red:focus,.btn-red:hover
+{
+ background:#600;
+ border:none
+}
+
+table
+{
+ border-collapse:separate;
+ margin-bottom:30px;
+ padding:20px;
+ background-color:#fff;
+ border:1px solid #e5e5e5;
+ box-shadow:0 1px 1px rgba(0,0,0,.04)
+}
+
+.table-small {
+ border-collapse:separate;
+ margin-bottom:0;
+ padding:4px;
+ background-color:#fff;
+ border:none;
+ box-shadow:none
+}
+
+table th
+{
+ background:#eee
+}
+
+table td,table th
+{
+ border-top:1px solid #e5e5e5;
+ /*border-right:1px solid #e5e5e5;*/
+ border-bottom:none;
+ border-left:1px solid #e5e5e5;
+ padding:10px;
+ border-collapse:collapse
+}
+
+.table-list-header
+{
+ background:#fff;
+ border-color:#ccc;
+ border-bottom:1px solid #efefef
+}
+
+.table-list-header .table-list-header-count
+{
+ margin-left:15px;
+ font-weight:700
+}
+
+.table-list-row
+{
+ padding:10px 15px;
+ border-color:#e5e5e5
+}
+
+.table-list-row.color-yellow
+{
+ border-left:solid 5px #e4c600!important
+}
+
+.table-list-row:hover
+{
+ border-right-color:#e5e5e5;
+ border-bottom-color:#e5e5e5;
+ border-top-color:#e5e5e5
+}
+
+.table-list-row .table-list-details
+{
+ line-height:22px;
+ margin-bottom:5px
+}
+
+.sidebar-content
+{
+ width:calc(100% - 240px)
+}
+
+@media (max-width: 480px) {
+ .sidebar-content
+ {
+ width:100%
+ }
+}
+
+.page-header
+{
+ background:#fff;
+ padding:10px 20px 10px 15px;
+ border:1px solid #e5e5e5;
+ box-shadow:0 1px 1px rgba(0,0,0,.04);
+ margin-top:15px;
+ margin-bottom:5px
+}
+
+.page-header:first-child
+{
+ border-top:none
+}
+
+.page-header h2
+{
+ font-size:1.3em;
+ border:none
+}
+
+.page-header ul
+{
+ margin-top:5px;
+ margin-bottom:5px
+}
+
+.page-header ul .fa
+{
+ color:#036
+}
+
+.page-header ul a
+{
+ text-decoration:none
+}
+
+.project-header
+{
+ background:#fff;
+ padding:12px;
+ margin-top:10px;
+ margin-bottom:10px;
+ overflow:auto;
+ border:1px solid #e5e5e5;
+ box-shadow:0 1px 1px rgba(0,0,0,.04)
+}
+
+.project-header .input-addon-item
+{
+ background:#fff;
+ border-color:#ccc
+}
+
+.sidebar-content .page-header ul
+{
+ margin-top:7px;
+ margin-bottom:3px
+}
+
+#board-container
+{
+ overflow-x:inherit
+}
+
+.board-add-icon
+{
+ padding-top:2px;
+ line-height:10px
+}
+
+.board-add-icon a i
+{
+ font-size:1em;
+ color:#3392cc
+}
+
+.board-add-icon a:hover i
+{
+ color:#036
+}
+
+.board-column-header-task-count
+{
+ color:#999;
+ font-size:.85em
+}
+
+.task-board
+{
+ padding:10px;
+ margin:2px 2px 6px
+}
+
+div.task-board-recent
+{
+ border-width:0
+}
+
+.task-board-icons,.task-list-icons
+{
+ font-size:.8em;
+ padding:4px;
+ background:rgba(255,255,255,0.41)
+}
+
+.task-board-category,.task-tags li
+{
+ padding:2px 6px
+}
+
+.task-tags li
+{
+ border-color:rgba(102,102,102,0.5);
+ color:#666;
+ background-color:rgba(255,255,255,0.5);
+ font-size:.9em
+}
+
+.project-overview-column strong
+{
+ font-size:2em
+}
+
+.project-overview-column
+{
+ border-radius:0;
+ min-width:100px;
+ margin-right:20px
+}
+
+.activity-event
+{
+ border-bottom:1px solid #e5e5e5;
+ padding:12px;
+ overflow:auto;
+ background:#fff
+}
+
+.activity-event:nth-child(even) {
+ background: #fff;
+}
+
+.activity-event:nth-child(even):hover
+{
+ background:rgba(51, 146, 204, 0.1)
+}
+
+.activity-event:hover {
+ background: rgba(51, 146, 204, 0.1);
+}
+
+.activity-title
+{
+ border:none
+}
+
+.editor-toolbar
+{
+ border-top-color:#ccc;
+ border-left-color:#ccc;
+ border-right-color:#ccc
+}
+
+.CodeMirror
+{
+ border-color:#ccc
+}
+
+#task-summary h2
+{
+ font-size:1.6em;
+ color:#333
+}
+
+.task-show-title h2
+{
+ font-size:1.6em;
+ color:#333
+}
+
+.comment-sorting
+{
+ font-size:11px
+}
+
+.task-summary-column
+{
+ color:#333
+}
+
+.task-list-tag
+{
+ background:#eee;
+ border-color:#ccc
+}
+
+.table-list-category
+{
+ border-color:#ccc
+}
+
+.subtask-cell
+{
+ border-color:#ccc
+}
+
+.subtask-cell:first-child
+{
+ padding-left:0
+}
+
+.task-list-subtasks
+{
+ margin-top:5px
+}
+
+.task-list-subtask:last-child .subtask-cell
+{
+ border-bottom:1px dotted #e5e5e5
+}
+
+.text-editor .text-editor-toolbar
+{
+ width:687px;
+ max-width:98%;
+ margin-top:20px;
+ margin-bottom:5px;
+ padding:12px;
+ background:#f2f2f2
+}
+
+.text-editor textarea
+{
+ width:696px;
+ max-width:98%
+}
+
+.alert
+{
+ margin-bottom:30px
+}
+
+.dropdown-submenu-open li:hover:not(.no-hover),.textarea-dropdown .active,.textarea-dropdown li:hover
+{
+ background:#3392cc
+}
+
+.accordion-title
+{
+ background:#fff;
+ padding:4px 0;
+ border-bottom:1px solid #ccc
+}
+
+.accordion-title h3
+{
+ background:0 0;
+ padding-left:0
+}
+
+.views li
+{
+ white-space:nowrap;
+ background:#fafafa;
+ border:1px solid #ddd;
+ border-right:none;
+ padding:8px;
+ display:inline
+}
+
+.pagination
+{
+ margin-bottom:30px
+}
+
+.activity-content
+{
+ margin-left:65px
+}
+
+#board_selector_chosen
+{
+ width:350px!important
+}
+
+#login-top
+{
+ margin-bottom: 20px;
+ text-align: center;
+}
+
+#login-bottom
+{
+ margin-top:40px
+}
+
+#login-bottom ul
+{
+ list-style-type:none
+}
+
+#login-bottom ul li
+{
+ margin-left:0
+}
+
+#modal-header
+{
+ margin-top:5px;
+ margin-bottom:5px;
+ font-size:1.5em;
+ float:right
+}
+
+#modal-content
+{
+ padding:0 20px
+}
+
+.documentation img
+{
+ border-color:#ccc;
+ margin-top:40px;
+ margin-bottom:20px
+}
+
+.documentation li
+{
+ line-height:1.6em
+}
+
+.documentation h1
+{
+ padding-bottom:5px;
+ border-bottom:1px solid #e5e5e5;
+ margin-top:40px;
+ margin-bottom:30px
+}
+
+.documentation h1:first-child
+{
+ margin-top:5px
+}
+
+.documentation h2
+{
+ padding-bottom:5px;
+ border-color:#e5e5e5;
+ margin-top:25px;
+ margin-bottom:10px
+}
+
+.documentation h3
+{
+ margin-top:25px;
+ margin-bottom:10px
+}
+
+.markdown h1
+{
+ margin-top:20px;
+ margin-bottom:10px;
+ padding-bottom:5px;
+ border-bottom:1px solid #e5e5e5
+}
+
+.markdown h2
+{
+ margin-top:20px;
+ margin-bottom:10px;
+ padding-bottom:5px;
+ border-bottom:1px solid #e5e5e5
+}
+
+.markdown h3
+{
+ margin-top:20px;
+ margin-bottom:10px
+}
+
+.markdown p
+{
+ margin-bottom:5px
+}
+
+@media print {
+ a.btn
+ {
+ display:none
+ }
+
+ header
+ {
+ display:block
+ }
+
+ header nav ul
+ {
+ display:none
+ }
+
+ .page-header
+ {
+ display:block
+ }
+
+ .page-header ul
+ {
+ display:none
+ }
+
+ .project-header
+ {
+ display:none
+ }
+
+ .filter-box
+ {
+ display:none
+ }
+
+ .dropdown ul
+ {
+ display:none
+ }
+
+ #board-container .board-add-icon
+ {
+ display:none
+ }
+
+ #board-container a.dropdown-menu i
+ {
+ display:none
+ }
+
+ #task-view .sidebar
+ {
+ display:block
+ }
+
+ #task-view .sidebar h2
+ {
+ display:none
+ }
+
+ #task-view .sidebar ul
+ {
+ display:none
+ }
+
+ #task-view .sidebar h2:first-child
+ {
+ display:block
+ }
+
+ #task-summary h2,.task-show-title h2,.task-summary-column span
+ {
+ color:#000
+ }
+
+ .task-summary-column .smaller
+ {
+ display:none
+ }
+
+ .accordion-collapsed
+ {
+ display:none
+ }
+
+ #comments .comment-sorting
+ {
+ display:none
+ }
+
+ #comments .comment-actions
+ {
+ display:none
+ }
+
+ a,th a
+ {
+ color:#000;
+ text-decoration:none
+ }
+
+ .table-fixed
+ {
+ white-space:normal
+ }
+
+ .table-fixed td
+ {
+ -o-text-overflow:clip;
+ text-overflow:clip;
+ white-space:normal
+ }
+
+ header nav h1 .logo
+ {
+ display:block
+ }
+
+ .page-header
+ {
+ background:0 0;
+ padding:0 0 10px
+ }
+
+ .page-header h2
+ {
+ margin:0
+ }
+
+ header nav h1 .tooltip i
+ {
+ display:none
+ }
+
+ #board-container
+ {
+ overflow-x:visible
+ }
+
+ #board-container .board-column-header
+ {
+ padding-left:7px
+ }
+
+ #task-view .sidebar h2:first-child
+ {
+ margin-top:50px;
+ margin-bottom:5px;
+ font-size:1.6em;
+ font-weight:400
+ }
+
+ #task-summary .task-summary-container
+ {
+ padding-right:80px
+ }
+
+ .accordion-title
+ {
+ background:0 0;
+ padding:0;
+ margin-top:40px
+ }
+
+ .accordion-title h3
+ {
+ margin:0
+ }
+
+ #comments .avatar
+ {
+ float:left;
+ width:48px
+ }
+
+ #comments .avatar .avatar-letter
+ {
+ color:#fff;
+ text-align:center
+ }
+
+ #comments .avatar-48 .avatar-letter
+ {
+ font-size:25px;
+ line-height:48px;
+ width:48px
+ }
+
+ #comments .avatar-48 div,#comments .avatar-48 img
+ {
+ border-radius:30px
+ }
+}
diff --git a/plugins/Customizer/Assets/css/userthemes/niebieski.css b/plugins/Customizer/Assets/css/userthemes/niebieski.css
new file mode 100644
index 00000000..2d68776c
--- /dev/null
+++ b/plugins/Customizer/Assets/css/userthemes/niebieski.css
@@ -0,0 +1 @@
+header{background-color:#006;background-image:linear-gradient(-180deg,transparent 0%,#FF3333 90%)}header h1,header a .fa{color:#36F}a i.web-notification-icon{color:}body{background:;color:}ul.dropdown-submenu-open,.accordion-title h3{background-color:}.dropdown-submenu-open a{color:}a:hover{color:}a .fa{color:}h1,h2,h3,.accordion-toggle{color:}.table-list-header a{color:}.table-list-header .table-list-header-count{color:}.table-list-row .table-list-title a{color:}.table-list-row .table-list-details strong{color:}.dropdown-menu-link-icon{color:}.page-header h2 a{color:}.sidebar>ul a:hover{color:}.sidebar>ul li.active a{color:}.task-list-icons a:hover{color:}.task-list-icons a:hover i{color:}.subtask-cell a{color:}a{color:}.table-list-category a:hover{color:}.subtask-cell a:hover,.subtask-cell a:focus{color:}a:focus,a:hover{color:}.table-list-header a:hover,.table-list-header a:focus,.task-board a{color:}.table-list-row .table-list-details{color:}.input-addon-field{color:}.page-header h2 a:focus,.page-header h2 a:hover,code{color:}.sidebar>ul a{color:}.task-list-avatars .task-avatar-assignee{color:}.task-list-icons a,.task-list-icons span,.task-list-icons i,.task-date{color:}span.task-date-overdue{color:}.sidebar>ul li.active{border-left:5px solid}.sidebar>ul li.active a:focus,.sidebar>ul li.active a:hover{color:}.sidebar>ul li:hover{border-left:5px solid}.panel{color:}td a.dropdown-menu strong,td a.dropdown-menu strong i{color:}#task-summary h2{color:unset}.comments .comment:hover{background:#efefef40}.comments .comment:nth-child(even):not(.comment-highlighted):hover{background:#efefef40}.comments .comment:nth-child(even):not(.comment-highlighted){background:#efefef40}table.table-striped tr:nth-child(odd){background:#efefef40}header{box-shadow:0 -1px 5px 1px;border-bottom:none}.project-header{margin-bottom:8px;margin-top:8px}.panel{background-color:#efefef40;border:1px solid #efefef40}.task-board{border-width:2px;background:#efefef22!important}div.task-board-recent{border-width:2px}table td{border:none}table th:first-child{border-top-left-radius:8px}table th:last-child{border-top-right-radius:8px}.table-list-header{background:#efefef40;border:1px solid #efefef40;border-radius:5px 5px 0 0;line-height:28px;padding-left:3px;padding-right:3px}.table-list-row{padding-left:3px;padding-right:3px;border-bottom:1px solid #efefef40;border-right:1px solid #efefef40}.table-list-row.table-border-left{border-left:1px solid #efefef40}.table-list-row:nth-child(odd){background:#efefef30}.table-list-row:hover{background:#efefef32;border-bottom:1px solid #efefef32;border-right:1px solid #efefef32}.dropdown-menu-link-icon{text-decoration:none}.dropdown-submenu-open li{border-bottom:1px solid #efefef40}.page-header h2{margin:0;padding:0;font-weight:700;border-bottom:1px dotted #efefef40}.sidebar>ul li{list-style-type:none;line-height:35px;border-bottom:1px dotted #efefef40;padding-left:13px}span.task-icon-age-total{border:1px solid #efefef40;padding:1px 3px 1px 3px;border-top-left-radius:3px;border-bottom-left-radius:3px}span.task-icon-age-column{border:1px solid #efefef40;border-left:none;margin-left:-5px;padding:1px 3px 1px 3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.subtask-cell{padding:4px 10px;border-top:1px dotted #efefef42;border-left:1px dotted #efefef40;display:table-cell;vertical-align:middle}table th{text-align:left;padding:.5em 3px;border:none;background:#efefef40}.views li{white-space:nowrap;background:#efefef40;border:none;border-right:none;padding:4px 8px;display:inline} \ No newline at end of file
diff --git a/plugins/Customizer/Assets/img/logo-gen.png b/plugins/Customizer/Assets/img/logo-gen.png
new file mode 100644
index 00000000..1df1986f
--- /dev/null
+++ b/plugins/Customizer/Assets/img/logo-gen.png
Binary files differ
diff --git a/plugins/Customizer/Assets/js/customizer.js b/plugins/Customizer/Assets/js/customizer.js
new file mode 100644
index 00000000..ae7c4f13
--- /dev/null
+++ b/plugins/Customizer/Assets/js/customizer.js
@@ -0,0 +1,248 @@
+//Functionality for a slider value feedback
+
+var header_logo_output = $('header_logo_output')[0];
+
+$(document).on('input', 'input[name="headerlogo_size"]', function(e) {
+ header_logo_output.innerHTML = e.currentTarget.value;
+ document.getElementById("hl1").style.height = e.currentTarget.value + "px";
+});
+
+var login_logo_output = $('login_logo_output')[0];
+
+$(document).on('input', 'input[name="loginlogo_size"]', function(e) {
+ login_logo_output.innerHTML = e.currentTarget.value;
+ document.getElementById("ll1").style.height = e.currentTarget.value + "px";
+});
+
+var av_icon_output = $('av_icon_output')[0];
+
+$(document).on('input', 'input[name="av_size"]', function(e) {
+ var siz = e.currentTarget.value;
+ av_icon_output.innerHTML = siz;
+ if (document.querySelector(".avatar-preview .avatar-letter") !== null) {
+ document.querySelector(".avatar-preview .avatar-letter").style.lineHeight = siz + "px";
+ document.querySelector(".avatar-preview .avatar-letter").style.width = siz + "px";
+ document.querySelector(".avatar-preview .avatar-letter").style.fontSize = (siz / 2) + "px";
+ } else {
+ var link = document.querySelector(".avatar-preview img").src;
+ var changedLink = link.substring(0, link.length-2);
+ document.querySelector(".avatar-preview img").src = changedLink + siz;
+ }
+});
+
+var av_radius_output = $('av_radius_output')[0];
+
+$(document).on('input', 'input[name="av_radius"]', function(e) {
+ var rad = e.currentTarget.value;
+ av_radius_output.innerHTML = rad;
+ if (document.querySelector(".avatar-preview .avatar-letter") !== null) {
+ document.querySelector(".avatar-preview .avatar-letter").style.borderRadius = rad + "%";
+ } else {
+ document.querySelector(".avatar-preview img").style.borderRadius = rad + "%";
+ }
+});
+
+var b_av_icon_output = $('b_av_icon_output')[0];
+
+$(document).on('input', 'input[name="b_av_size"]', function(e) {
+ var siz = e.currentTarget.value;
+ b_av_icon_output.innerHTML = siz;
+ if (document.querySelector(".b-avatar-preview .avatar-letter") !== null) {
+ document.querySelector(".b-avatar-preview .avatar-letter").style.lineHeight = siz + "px";
+ document.querySelector(".b-avatar-preview .avatar-letter").style.width = siz + "px";
+ document.querySelector(".b-avatar-preview .avatar-letter").style.fontSize = (siz / 2) + "px";
+ } else {
+ var link = document.querySelector(".b-avatar-preview img").src;
+ var changedLink = link.substring(0, link.length-2);
+ document.querySelector(".b-avatar-preview img").src = changedLink + siz;
+ }
+});
+
+var b_av_radius_output = $('b_av_radius_output')[0];
+
+$(document).on('input', 'input[name="b_av_radius"]', function(e) {
+ var rad = e.currentTarget.value;
+ b_av_radius_output.innerHTML = rad;
+ if (document.querySelector(".b-avatar-preview .avatar-letter") !== null) {
+ document.querySelector(".b-avatar-preview .avatar-letter").style.borderRadius = rad + "%";
+ } else {
+ document.querySelector(".b-avatar-preview img").style.borderRadius = rad + "%";
+ }
+});
+
+
+//Accordion for settings page
+
+document.addEventListener("DOMContentLoaded", function(event) {
+
+
+ var acc = document.getElementsByClassName("login-accordion");
+ var panel = document.getElementsByClassName('login-accordian-panel');
+
+ for (var i = 0; i < acc.length; i++) {
+ acc[i].onclick = function() {
+ var setClasses = !this.classList.contains('current');
+ setClass(acc, 'current', 'remove');
+ setClass(panel, 'show', 'remove');
+
+ if (setClasses) {
+ this.classList.toggle("current");
+ this.nextElementSibling.classList.toggle("show");
+ }
+ }
+ }
+
+ function setClass(els, className, fnName) {
+ for (var i = 0; i < els.length; i++) {
+ els[i].classList[fnName](className);
+ }
+ }
+});
+
+// Valid for the form id="settings" checkboxes, when the "change" event occurs, the module is sent.
+
+$(document).ready(function(){
+ $("#settings").on("change", "input:checkbox", function(){
+ $("#settings").submit();
+ });
+});
+
+// auto submit on change for user theme
+if (document.getElementById('userthemeSelection')) {
+ document.getElementById('userthemeSelection').onchange = function() {
+ document.getElementById('ts').submit();
+ }
+}
+
+//Live Preview
+
+if (document.getElementById('loginpanel_color')) {
+ document.getElementById('loginpanel_color').oninput = function() {
+ document.getElementById('preview-form-login').style.backgroundColor = document.getElementById('loginpanel_color').value;
+ document.getElementById('preview-form-note').style.backgroundColor = document.getElementById('loginpanel_color').value;
+ }
+
+ document.getElementById('loginbackground_color').oninput = function() {
+ document.getElementById('preview').style.backgroundColor = document.getElementById('loginbackground_color').value;
+ }
+
+ document.getElementById('form-login_note').oninput = function() {
+ document.getElementById('preview-form-note').innerHTML = document.getElementById('form-login_note').value;
+ }
+
+ document.getElementById('login_shadow_color').oninput = function() {
+ var slider = document.getElementById("login_shadow").value;
+ var color = document.getElementById('login_shadow_color').value;
+ document.getElementById('preview-form-login').style.boxShadow = '0px 0px ' + slider + 'px ' + slider * 0.1 + 'px ' + color;
+ document.getElementById('preview-form-note').style.boxShadow = '0px 0px ' + slider + 'px ' + slider * 0.1 + 'px ' + color;
+ }
+
+ document.getElementById('login_border_color').oninput = function() {
+ document.getElementById('preview-form-login').style.borderColor = document.getElementById('login_border_color').value;
+ document.getElementById('preview-form-note').style.borderColor = document.getElementById('login_border_color').value;
+ }
+
+ document.getElementById('login_btn_color').oninput = function() {
+ document.getElementById('preview-login-btn').style.backgroundColor = document.getElementById('login_btn_color').value;
+ }
+
+ document.getElementById('login_btn_shadow_color').oninput = function() {
+ var slider = document.getElementById("login_btn_shadow").value;
+ var color = document.getElementById('login_btn_shadow_color').value;
+ document.getElementById('preview-login-btn').style.boxShadow = '0px 0px ' + slider + 'px ' + slider * 0.1 + 'px ' + color;
+ }
+
+ document.getElementById('login_btn_font_color').oninput = function() {
+ document.getElementById('preview-login-btn').style.color = document.getElementById('login_btn_font_color').value;
+ }
+
+ document.getElementById('login_btn_shade_color').oninput = function() {
+ var color = document.getElementById('login_btn_shade_color').value;
+ document.getElementById('preview-login-btn').style.backgroundImage = 'linear-gradient(-180deg, transparent 0%, ' + color + ' 90%';
+ }
+
+ document.getElementById('login_btn_shadow').oninput = function() {
+ var slider = document.getElementById("login_btn_shadow").value;
+ var color = document.getElementById('login_btn_shadow_color').value;
+ document.getElementById('preview-login-btn').style.boxShadow = '0px 0px ' + slider + 'px ' + slider * 0.1 + 'px ' + color;
+ }
+
+ document.getElementById('login_btn_border').oninput = function() {
+ var slider = document.getElementById("login_btn_border").value;
+ var color = document.getElementById('login_btn_border_color').value;
+ document.getElementById('preview-login-btn').style.border = slider + 'px solid ' + color;
+ }
+
+ document.getElementById('login_border').oninput = function() {
+ var slider = document.getElementById("login_border").value;
+ var color = document.getElementById('login_border_color').value;
+ document.getElementById('preview-form-login').style.border = slider + 'px solid ' + color;
+ document.getElementById('preview-form-note').style.border = slider + 'px solid ' + color;
+ }
+
+ document.getElementById('login_shadow').oninput = function() {
+ var slider = document.getElementById("login_shadow").value;
+ var color = document.getElementById('login_shadow_color').value;
+ document.getElementById('preview-form-login').style.boxShadow = '0px 0px ' + slider + 'px ' + slider * 0.1 + 'px ' + color;
+ document.getElementById('preview-form-note').style.boxShadow = '0px 0px ' + slider + 'px ' + slider * 0.1 + 'px ' + color;
+ }
+
+ document.getElementById('login_btn_width').oninput = function() {
+ var slider = document.getElementById("login_btn_width").value;
+ document.getElementById('preview-login-btn').style.width = slider + 'px';
+ }
+
+ document.getElementById('preview-login-btn').onmouseover = function() {
+ document.getElementById('preview-login-btn').style.color = document.getElementById('login_btn_color').value;
+ document.getElementById('preview-login-btn').style.backgroundColor = document.getElementById('login_btn_font_color').value;
+ }
+
+ document.getElementById('preview-login-btn').onmouseout = function() {
+ document.getElementById('preview-login-btn').style.backgroundColor = document.getElementById('login_btn_color').value;
+ document.getElementById('preview-login-btn').style.color = document.getElementById('login_btn_font_color').value;
+ }
+
+ document.getElementById('form-background_url').oninput = function() {
+ var val = document.getElementById("form-background_url").value;
+ document.getElementById('preview').style.background = 'url("' + val +'") no-repeat center center';
+ document.getElementById('preview').style.backgroundSize = 'cover';
+ }
+}
+
+function OnColorChanged(selectedColor, inputId) {
+ if (inputId == "login_shadow_color") {
+ var slider = document.getElementById("login_shadow").value;
+ document.getElementById("preview-form-login").style.color = selectedColor;
+ document.getElementById("preview-form-note").style.boxShadow = '0px 0px ' + slider + 'px ' + slider * 0.1 + 'px ' + selectedColor;
+ }
+ if (inputId == "loginbackground_color") {
+ var rgbaBox = document.getElementById("preview");
+ rgbaBox.style.backgroundColor = selectedColor;
+ }
+ if (inputId == "loginpanel_color") {
+ document.getElementById("preview-form-login").style.backgroundColor = selectedColor;
+ document.getElementById("preview-form-note").style.backgroundColor = selectedColor;
+ }
+ if (inputId == "login_border_color") {
+ document.getElementById("preview-form-login").style.borderColor = selectedColor;
+ document.getElementById("preview-form-note").style.borderColor = selectedColor;
+ }
+ if (inputId == "login_btn_color") {
+ var rgbaBox = document.getElementById("preview-login-btn");
+ rgbaBox.style.backgroundColor = selectedColor;
+ }
+ if (inputId == "login_btn_shadow_color") {
+ var rgbaBox = document.getElementById("preview-login-btn");
+ var slider = document.getElementById("login_btn_shadow").value;
+ rgbaBox.style.boxShadow = '0px 0px ' + slider + 'px ' + slider * 0.1 + 'px ' + selectedColor;
+ }
+ if (inputId == "login_btn_font_color") {
+ var rgbaBox = document.getElementById("preview-login-btn");
+ rgbaBox.style.color = selectedColor;
+ }
+ if (inputId == "login_btn_shade_color") {
+ var rgbaBox = document.getElementById("preview-login-btn");
+ rgbaBox.style.backgroundImage = 'linear-gradient(-180deg, transparent 0%, ' + selectedColor + ' 90%';
+ }
+}
+
diff --git a/plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.css b/plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.css
new file mode 100644
index 00000000..6db3d7b3
--- /dev/null
+++ b/plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.css
@@ -0,0 +1,167 @@
+/*! Menucool rgba Color Picker v2018.9.12. www.menucool.com/rgba-color-picker */
+
+input.color{
+ width:110px;
+ height: 18px;
+ font-size:11px;
+ border:1px solid #dadada;
+ box-sizing:content-box;
+ padding-left:4px;
+}
+
+#colorpicker
+{
+ padding: 20px;
+ position: absolute;
+ top: 22px;
+ left:auto; right:0;/*If need align to the right, set: left:0;right:auto; */
+ background-color: #FFF;
+ border: 1px solid #BBB;
+ display: none;
+ z-index: 200;
+ box-sizing:content-box;
+ font: normal 10px verdana;
+ color: #666;
+ border-radius:4px;
+ box-shadow:0 1px 8px rgba(0,0,0,0.5);
+}
+
+input.color.up + .colorChooser > #colorpicker {
+ top: -306px;
+}
+
+#colorpicker div
+{
+ float:left;
+ padding:0;
+ box-sizing:content-box;
+}
+#colorpicker div.clear, #colorpicker div.separator
+{
+ float: none;
+ clear: both;
+ border: 0;
+ overflow:hidden;
+ height:0;
+ font-size:0;
+}
+#colorpicker div.separator
+{
+ margin-bottom:8px;
+}
+
+#colorpicker #colorContainer
+{
+ border:0;
+ border-right: 1px solid black;
+ border-bottom: 1px solid black;
+ cursor: pointer;
+ font-size:0;
+ width:234px;
+}
+#colorContainer div
+{
+ border:0;
+ border-top: solid 1px black;
+ border-left: solid 1px black;
+ width:12px;
+ height:12px;
+ overflow:hidden;
+}
+#colorpicker .w1, #colorpicker .w2 {
+ width:80px;
+ padding-left:4px;
+ border: 1px solid #999;
+ border-radius:3px;
+ position:relative;
+}
+div.w1, div.w2 {
+ height: 22px;
+ line-height: 22px;
+}
+input.w1, input.w2 {
+ font: normal 10px verdana;
+ height: 18px;
+ line-height: 18px;
+}
+#colorpicker .w2 {
+ width:120px;
+ float:right;
+
+}
+#colorpicker div.w2::before {
+ content: " ";
+ width:100%;
+ height:22px;
+ position:absolute;
+ left:0;top:0;
+ z-index:-1;
+}
+.opacitySpan {display:inline-block;padding-top:4px;}
+
+input.rgbaRange {
+ width:118px;
+ margin-left:20px;
+ vertical-align:middle;
+}
+/*brennaobrien.com/blog/2014/05/style-input-type-range-in-every-browser.html*/
+input[type=range]{
+ -webkit-appearance: none;
+ border:none;
+}
+
+input[type=range]::-webkit-slider-runnable-track {
+ width: 118px;
+ height: 5px;
+ background: #ddd;
+ border: none;
+ border-radius: 2px;
+}
+
+input[type=range]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ border: none;
+ height: 15px;
+ width: 15px;
+ border-radius: 50%;
+ background: #888;
+ margin-top: -5px;
+}
+
+input[type=range]:focus {
+ outline: none;
+}
+
+input[type=range]:focus::-webkit-slider-runnable-track {
+ background: #ccc;
+}
+
+#colorpicker .btnOK {
+ width:40px;
+ float:right;
+ height:22px;
+ font-size: 12px;
+ vertical-align:middle;
+ cursor:pointer;
+}
+
+.transChooser, #colorpicker div.w2::before {
+ background: white url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAAB90RVh0U29mdHdhcmUATWFjcm9tZWRpYSBGaXJld29ya3MgOLVo0ngAAAAWdEVYdENyZWF0aW9uIFRpbWUAMDkvMDcvMTgBUiN5AAAAKklEQVQokWNkYGDwZcAC/v//j02YgQmrKB4wHDSw4AoNRkZG6tgwHDQAAMuIBmTkVeR2AAAAAElFTkSuQmCC') repeat;
+}
+
+/*Click span.colorChooser will popup the div#colorpicker */
+.colorChooser {
+ width: 46px;
+ height: 20px;
+ border: 1px solid rgba(0,0,0,0.3);
+ display: inline-block;
+ overflow: visible;
+ vertical-align: middle;
+ position: relative;
+ box-sizing: content-box;
+ border-radius: 0 3px 3px 0;
+ user-select: none;
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAICAYAAAAftBSpAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAAB90RVh0U29mdHdhcmUATWFjcm9tZWRpYSBGaXJld29ya3MgOLVo0ngAAAAWdEVYdENyZWF0aW9uIFRpbWUAMDkvMjIvMThMTIArAAAAvklEQVQokZXMrw6CUBiG8dc/xUICIpliOHMjH4vZeQU2LV6BmWD2DrwYNgMJyqGQSNC4gMfEpk5xftvbnu83ASTpLmmu/y+QtBYgYO+cI4oiJP2c53lkWQZwAxYDIuBUliW+748CQRCQ5znAFfABPSMCjm3bEsfxRyAMQ5qmATg//70jU2BXFAVJkrwAxhiccwAHYDaGDNv0fY+1FklYa6nrGmD7qf+GCDBVVZGmKV3XAay+tWOIAANcgOVY9wCltDLGu2gdigAAAABJRU5ErkJggg==');
+ background-position:center center;
+ background-repeat:no-repeat;
+}
diff --git a/plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.js b/plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.js
new file mode 100644
index 00000000..6e7de940
--- /dev/null
+++ b/plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.js
@@ -0,0 +1,492 @@
+/*r
+ColorPickerCallback:cpC
+element:r
+hexInput:a
+bgDiv1:b
+bgDiv2:c
+initIt:d
+dColorContainer:e
+appendSeparator:f
+createColorDivs:g
+stopPropagation:h
+addColorElements:j
+createElm:k
+
+clrContainerEvtHdler_getSelectedColorAndThenUpdateInnerValues:m
+
+createDropdownSpans:o
+
+
+initColorPickerValues:s
+setColorPickerInsideValues:t
+
+rgbaInput:v
+iRgbaRange:w
+getParsedColors:x
+r*/
+
+/*
+Knowledge: 1. backgound-color:none; color:none; are not a valid color.
+ 2. Convert rgba to similar hex color: https://stackoverflow.com/questions/15898740/how-to-convert-rgba-to-a-transparency-adjusted-hex
+*/
+
+/*! Menucool rgba Color Picker v2018.9.23. menucool.com/rgba-color-picker */
+var MenuCoolRgbaColorPickerOptions = {
+ initOnPageLoad: true
+};
+
+var rgbaColorPicker = (function (myOptions) {
+ 'use strict';
+ // Private members
+ var addEvent = function (elem, evtType, func) {
+ if (elem.addEventListener) {
+ elem.addEventListener(evtType, func, false);
+ } else if (elem.attachEvent) {
+ elem.attachEvent("on" + evtType, func);
+ }
+ else {
+ // for IE/Mac, NN4, and older
+ elem["on" + evtType] = func;
+ }
+ };
+ var classIsColor = function (myClass) {
+ if (!myClass) return 0;
+ var pattern = /\bcolor\b/;
+ return pattern.test(myClass);
+ };
+ var len = "length";
+ var documentCreateElement = function (tagName) {
+ return document.createElement(tagName);
+ };
+ var hexToRgb = function (hex) {
+ var retVal = 0;
+ if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
+ var c = hex.substring(1).split('');
+ if (c[len] == 3) {
+ c = [c[0], c[0], c[1], c[1], c[2], c[2]];
+ }
+ c = '0x' + c.join('');
+ retVal = [(c >> 16) & 255, (c >> 8) & 255, c & 255];
+ }
+ return retVal;
+ };
+ var hexAlphaToRgba = function (hex, alpha) {
+ var retVal = hexToRgb(hex);
+ return retVal ? 'rgba(' + retVal.join(',') + ',' + alpha + ')' : _invalid;
+ };
+
+ var rgbaToSimilarHex = function (rgba, bgHexColor) {
+ var hex = '';
+ var match = rgba.match(/rgba\((\d+),(\d+),(\d+),([.\d]+)/i);
+ if (match) {
+ var rgbBg = hexToRgb(bgHexColor);
+ var rgbConverted = [];
+ var alpha = +match[4];
+ //now convert hex+alpha to similar hex. //rgba to similar rbg: Color = Color * alpha + Bkg * (1 - alpha);
+ for (var i = 0; i < 3; i++) {
+ rgbConverted.push(Math.floor(+match[i+1] * alpha + (+rgbBg[i]) * (1 - alpha)));
+ }
+ hex = rgbToHex(rgbConverted.join(','));
+ //console.log(hex, rgba);
+ }
+ return hex;
+ };
+
+ var componentToHex = function (c) {
+ var hex = c.toString(16).toUpperCase();
+ return hex[len] == 1 ? "0" + hex : hex;
+ };
+ var rgbToHex = function (rgbStr) {
+ var rgb = rgbStr.split(',');
+ return "#" + componentToHex(+rgb[0]) + componentToHex(+rgb[1]) + componentToHex(+rgb[2]);
+ };
+ /* //jsfiddle.net/salman/f9Re3/
+ var invertColor = function (color) {
+ color = parseInt(color.substring(1), 16); // convert to integer
+ color = 0xFFFFFF ^ color; // invert three bytes
+ color = color.toString(16); // convert to hex
+ color = "#" + ("000000" + color).slice(-6); // pad with leading zeros, also prepend #
+ return color;
+ };*/
+ var validColorNumbers = function (nums) {
+ var numArr = nums.split(',');
+ for (var i = 0; i < numArr[len]; i++) {
+ if (+numArr[i] < 0 || +numArr[i] > 255) return 0;
+ }
+ return 1;
+ };
+
+ var isiOS = (navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false);
+
+ var clickOrTouch = isiOS ? "touchstart" : "click";
+
+ var clsNm = "className", sty = "style", bgClr = "backgroundColor", dspl = "display", val = "value", _invalid = "invalid", appendCld = "appendChild", trans = "transparent", undef = "undefined";
+
+ var getStyle = function (elm) {
+ if (window.getComputedStyle) //modern browsers
+ var computedStyle = window.getComputedStyle(elm, null); //null can be replaced with pseudo such as 'hover'
+ else if (elm.currentStyle) //IE
+ computedStyle = elm.currentStyle;
+ else
+ computedStyle = elm[sty];
+
+ return computedStyle;
+ };
+
+ var picker, //the picker instance
+ HexInSession;//unconfirmed chosen hex color
+
+ var btnOKClickHandler = function () {
+ if (picker) {
+ if (+picker.iRgbaRange[val] == 1)
+ var color = picker.hexInput[val];
+ else
+ color = picker.rgbaInput[val];
+ picker.R[picker.i][val] = color;
+ picker.R[picker.i].onchange();
+ picker.element[sty][dspl] = "none";
+ if (typeof OnColorChanged !== "undefined") OnColorChanged(color, picker.R[picker.i].id);
+ }
+ };
+
+ //Picker class constructor
+ var Picker = function () {
+ var that = this;
+ that.hexInput = //div elelment for displaying selected color as its text
+ that.bgDiv1 = //div elelment for displaying selected color as its background
+ that.bgDiv2 =
+ that.dColorContainer = null; //div element containing colors
+ that.i = -1; //current target index
+ that.R = []; //target inputs that class contains "color"
+ that.S = []; //span elements after the R target inputs: the chooser
+
+ that.initIt();
+ };
+
+ Picker.prototype = {
+ appendSeparator: function (div) {
+ var sep = documentCreateElement("div");
+ if (!div) {
+ div = this.element;
+ sep[clsNm] = "separator";
+ }
+ else
+ sep[clsNm] = "clear";
+ div[appendCld](sep);
+ },
+
+ createColorDivs: function (r, b, g) {
+ var colorCell = documentCreateElement("div");
+ if (r == "TT") {
+ colorCell[clsNm] = "transChooser";
+ colorCell.setAttribute("rgb", trans);
+ }
+ else {
+ colorCell[sty][bgClr] = "#" + r + g + b;
+ colorCell.setAttribute("rgb", "#" + r + g + b);
+ }
+ return colorCell;
+ },
+
+ stopPropagation: function (e) {
+ e = e ? e : window.event;
+ e.cancelBubble = true;
+ if (e.stopPropagation) e.stopPropagation();
+ },
+
+ addColorElements: function () {
+ var that = this;
+ var colorCell;
+ var div = that.dColorContainer;
+
+ //add grayscales to div
+ var grays = ["00", "11", "22", "33", "44", "55", "66", "77", "88", "99", "AA", "BB", "CC", "DD", "EE", "F6", "FF", "TT"];
+ for (var a = 0; a < 18; a++) {
+ colorCell = that.createColorDivs(grays[a], grays[a], grays[a]);
+ div[appendCld](colorCell);
+ }
+ that.appendSeparator(div);
+
+ //add colors to div (first group)
+ var c = ["00", "33", "66", "99", "CC", "FF"];
+ for (var b = 0; b < 6; b++) {
+ for (var r = 0; r < 3; r++) {
+ for (var g = 0; g < 6; g++) {
+ colorCell = that.createColorDivs(c[r], c[g], c[b]);
+ div[appendCld](colorCell);
+ }
+ }
+ that.appendSeparator(div);
+ }
+ that.appendSeparator(div);
+
+ //add colors to div (second group)
+ for (var b = 0; b < 6; b++) {
+ for (var r = 3; r < 6; r++) {
+ for (var g = 0; g < 6; g++) {
+ colorCell = that.createColorDivs(c[r], c[g], c[b]);
+ div[appendCld](colorCell);
+ }
+ }
+ that.appendSeparator(div);
+ }
+ },
+ //type: null-div, 1-span, 2-input[type=text], 3-input[type=range], 4-button
+ createElm: function (id, type) {
+ var tagName;
+ switch (type) {
+ case 1:
+ tagName = "span";
+ break;
+ case 2:
+ case 3:
+ tagName = "input";
+ break;
+ case 4:
+ tagName = "button";
+ break;
+ default:
+ tagName = "div";
+ break;
+ }
+ var el = documentCreateElement(tagName);
+ if (id[0] == '#')
+ el.id = id.substring(1);
+ else
+ el[clsNm] = id;
+
+ if (type == 2) {
+ el.type = "text";
+ el.setAttribute("spellcheck", "false");
+ }
+ else if (type == 3) {
+ el.type = "range";
+ }
+ if (id != "#colorpicker" && id != "colorChooser")
+ this.element[appendCld](el);
+ return el;
+ },
+
+ initIt: function () {
+ var that = this;
+ // 1. create color picker
+ that.element = that.createElm("#colorpicker");
+ addEvent(that.element, clickOrTouch, that.stopPropagation);
+
+ that.bgDiv1 = that.createElm("w1");
+ that.bgDiv2 = that.createElm("w2");
+ that.appendSeparator();//----------
+ that.hexInput = that.createElm("w1", 2);
+ that.rgbaInput = that.createElm("w2", 2);
+ that.appendSeparator();//----------
+ var btnOK = that.createElm("btnOK", 4);//it will float to right
+ btnOK.setAttribute("type", "button");//avoid submitting form
+ btnOK.innerHTML = "OK";
+ var opacitySpan = that.createElm("opacitySpan", 1);
+ opacitySpan.innerHTML = "Opacity";
+ that.iRgbaRange = that.createElm("rgbaRange", 3);
+ that.appendSeparator();//----------
+
+ that.dColorContainer = that.createElm("#colorContainer");
+ addEvent(that.dColorContainer, "mouseover", function (e) { that.clrContainerEvtHdler_getSelectedColorAndThenUpdateInnerValues(e, 1); });
+ addEvent(that.dColorContainer, "mouseout", function (e) { that.clrContainerEvtHdler_getSelectedColorAndThenUpdateInnerValues(e, 2); });
+ //The following eventType ==3 will also update HexInSession
+ addEvent(that.dColorContainer, clickOrTouch, function (e) { that.clrContainerEvtHdler_getSelectedColorAndThenUpdateInnerValues(e, 3); });
+
+ //that.iRgbaRange.type = "range";
+ that.iRgbaRange[val] = 1;
+ that.iRgbaRange.min = 0;
+ that.iRgbaRange.max = 1;
+ that.iRgbaRange.step = 0.1;
+ addEvent(that.iRgbaRange, "input", function () { that.setColorPickerInsideValues(that.hexInput[val]); });
+
+
+ // 2. populate color picker cells
+ that.addColorElements();
+
+ // 3. create spans after all class="color" elements, set popup events for them
+ that.createDropdownSpans();
+
+ // 4. click on body will hide the #colorpicker
+ //I add this event to document.documentElement other than document.body as sometimes I clicked <html> instead of <body>
+ addEvent(document.documentElement, clickOrTouch, function () { that.element[sty][dspl] = "none"; });
+ addEvent(btnOK, clickOrTouch, btnOKClickHandler);
+
+ //if (typeof OnColorPickerLoaded != undef) OnColorPickerLoaded();
+
+ },
+
+ clrContainerEvtHdler_getSelectedColorAndThenUpdateInnerValues: function (e, eventType) {
+ if (eventType == 2) //mouseout of colorContainer
+ {
+ var selectedColor = HexInSession;
+ }
+ else{ //mouseover, or clickOrTouch
+ if (e.target) var target = e.target; //recognized by all except IE
+ else target = e.srcElement; //IE only knows srcElement. Chrome, Safari, Opera also knows it.
+ if (target.id != "colorContainer"){
+ selectedColor = target.getAttribute("rgb");
+ if (eventType == 3) HexInSession = selectedColor;
+ }
+ }
+ if(selectedColor)
+ picker.setColorPickerInsideValues(selectedColor);
+ //picker.stopPropagation(e);//don't need it anymore as we now have: addEvent(that.element, "click", that.stopPropagation);
+ },
+
+ // create choosers(span) after all class="color" elements, set popup events for them
+ createDropdownSpans: function () {
+ var colorInputs = document.getElementsByTagName("input");
+ var that = this;
+ for (var j = 0; j < colorInputs[len]; j++) {
+ if (classIsColor(colorInputs[j][clsNm])) { //search all input element who has "color" class
+ var i = that.R[len];
+ that.R[i] = colorInputs[j]; //that.R.push(colorInputs); will make the R[i].parentNode throw exception
+ that.R[i].i = i;
+ //create choosers
+ that.S[i] = that.createElm("colorChooser", 1);
+ that.S[i].i = i;
+ that.S[i].id = colorInputs[j]['name'];
+
+ //that.S[i].arrow = that.createElm("colorChooserArrow", 1);
+ //that.S[i][appendCld](that.S[i].arrow);
+
+ that.R[i].parentNode.insertBefore(that.S[i], that.R[i].nextSibling);
+ that.S[i][sty][bgClr] = that.R[i][val];
+ addEvent(that.S[i], clickOrTouch, function (e) {
+ if (that.element.parentNode == this && that.element[sty][dspl] == "block") that.element[sty][dspl] = "none";
+ else {
+ that.i = this.i;
+ this[appendCld](that.element)[sty][dspl] = "block";
+ that.initColorPickerValues();
+ }
+ that.stopPropagation(e);
+ });
+
+ //now we know what is S, so we can add the following event
+ that.R[i].onchange = function () {
+ that.S[this.i][sty][bgClr] = this[val];
+ };
+
+ if (typeof OnColorChanged !== "undefined") OnColorChanged(that.R[i][val], that.R[i].id);
+ }
+ }
+ },
+
+
+ //called by chooser click event handler: it will set values and BGs based on R[i][val]
+ initColorPickerValues: function () {
+ var that = this;
+ var parsedColor = that.getParsedColors(that.R[that.i][val]);
+ HexInSession = parsedColor[0];
+ that.iRgbaRange[val] = parsedColor[len] == 2 ? parsedColor[1] : 1;
+ if (HexInSession == _invalid) {
+ that.bgDiv1[sty][bgClr] = that.bgDiv2[sty][bgClr] = trans;
+ that.hexInput[val] = that.rgbaInput[val] = _invalid;
+ }
+ else
+ that.setColorPickerInsideValues(HexInSession);
+
+ },
+ ///possible return values: [''], ['transparent'], ['invalid'], ['#......'], ['#......', num <=1]
+ getParsedColors: function (color) { //,chooser
+ color = color.replace(/\s+/g, '').toLowerCase();
+ var retVal = [_invalid];
+ if (!color || color == trans) {
+ retVal = [color];
+ }
+ else if (color[0] == '#') {
+ if (/^#([a-f0-9]{3}){1,2}$/.test(color)) {
+ retVal = [color];
+ }
+ }
+ else if (/^rgba\(\d+,\d+,\d+,[\.\d]+\)$/.test(color)) {
+ var match = color.match(/^rgba\((\d+,\d+,\d+),([\.\d]+)\)$/);
+ if (match) {
+ if (validColorNumbers(match[1]) && +match[2] <= 1) {
+ retVal = [rgbToHex(match[1]), +match[2]];
+ }
+ }
+ }
+ else if (/^rgb\(\d+,\d+,\d+\)$/.test(color)) {
+ var match = color.match(/^rgb\((\d+,\d+,\d+)\)$/);
+ if (validColorNumbers(match[1])) {
+ retVal = [rgbToHex(match[1])];
+ }
+ }
+ else { //color name such as red, gray. Chrome will change bad name to 'rgba(0,0,0,0)', IE will be 'transparent'
+ var testBox = this.bgDiv1; //chooser ? chooser : this.bgDiv1;
+ testBox[sty][bgClr] = trans;//why having this line of code? It is a bug fix: If color is not a valid color, the next line won't change the previous color, and computedColor will still get the previous color. So we first change it to "transparent"
+ testBox[sty][bgClr] = color;
+ var computedColor = getStyle(testBox)[bgClr];
+
+ //console.log("computedColor", computedColor, color, chooser);
+ if (computedColor.indexOf('rgb(') != -1) {
+ retVal = [rgbToHex(computedColor.replace('rgb(', '').replace(')', ''))];
+ }
+ }
+ return retVal;
+ },
+
+ //called by many events. It will update two inputs and two bg divs
+ setColorPickerInsideValues: function (hex) {
+ var alpha = +picker.iRgbaRange[val];
+ picker.bgDiv1[sty][bgClr] = picker.hexInput[val] = (hex && hex[0] == '#' ? hex.toUpperCase() : hex);
+ picker.bgDiv2[sty][bgClr] = picker.rgbaInput[val] = (hex && hex[0] == '#' ? hexAlphaToRgba(hex, alpha) : hex);//the later hex will be (!hex || hex == 'transparent')
+ }
+ };
+
+ var buildPicker = function () {
+ if (!picker) picker = new Picker();
+ /*if (isiOS) {
+ //stackoverflow.com/questions/17567344/detect-left-right-swipe-on-touch-devices-but-allow-up-down-scrolling
+ //The following defines the touch event handlers for: fast click : 'fc', swipe horizontal: 'swh', swipe vertical: 'swv'
+ (function (d) {
+ var
+ ce = function (e, n) { var a = document.createEvent("CustomEvent"); a.initCustomEvent(n, true, true, e.target); e.target.dispatchEvent(a); a = null; return false },
+ nm = true, sp = { x: 0, y: 0 }, ep = { x: 0, y: 0 },
+ touch = {
+ touchstart: function (e) { sp = { x: e.touches[0].pageX, y: e.touches[0].pageY } },
+ touchmove: function (e) { nm = false; ep = { x: e.touches[0].pageX, y: e.touches[0].pageY } },
+ touchend: function (e) { if (nm) { ce(e, 'fc') } else { var x = ep.x - sp.x, xr = Math.abs(x), y = ep.y - sp.y, yr = Math.abs(y); if (Math.max(xr, yr) > 20) { ce(e, (xr > yr ? 'swh' : 'swv')) } }; nm = true },
+ touchcancel: function (e) { nm = false }
+ };
+ for (var a in touch) { d.addEventListener(a, touch[a], false); }
+ })(picker.element);
+ }*/
+ };
+
+ if (myOptions.initOnPageLoad)
+ addEvent(window, "load", buildPicker);
+
+ return {
+ //reload code below not work. Maybe the removed stuff is still there as it might be referenced somewhere else. So comment it out!
+ //reload: function () {
+ // if (picker.S && picker.S.length) {
+ // console.log(picker.S.length);
+ // for (var i = 0; i < picker.S.length; i++) {
+ // picker.S[i].parentNode.removeChild(picker.S[i]);
+ // picker.S[i] = null;
+ // }
+ // }
+ // picker.R=picker.S=[];
+ // picker.createDropdownSpans();
+ //},
+ hexAlphaToRgba: hexAlphaToRgba, //(hex, alpha) such as hexAlphaToRgba("#DD9999", 0.5)
+ rgbToHex: rgbToHex, //(rgbStr) such as rgbToHex("255,0,0")
+ rgbaToHex: rgbaToSimilarHex, //(rgba, bgHexColor) such as rgbaToSimilarHex("rgba(255,0,0,0.2)", "#DD9999")
+ init: buildPicker //when option is !MenuCool.cpInitOnLoad, you need to call init() manually when you are ready to populate color picker
+ };
+})(MenuCoolRgbaColorPickerOptions);
+
+/*
+ChangeSet #1 (2012-2-10): I use colorContainerEventDelegate to replace each color cell's onclick event. Greatly decreased the number of event listeners.
+ChangeSet #2 (2012-2-10): Use stopPropagation instead of timer to fix the event bubbling to body that will hide the color picker.
+ChangeSet #3 (2012-2-11): add if(target.id!="colorContainer") to fix the IE7 bug that the even responde to element "colorContainer".
+ChangeSet #4 (2012-6-27): add to support Ajax by calling reload. Requested by Birger on 6.26 (check email)
+ChangeSet #5 (2012-6-28): add transparent color to mcColorPicker.
+ChangeSet #6 (2012-8-30): Don't need the menucool link anymore.
+ChangeSet #7 (2014-9-29): DOMContentLoaded instead of window.onload; added DOMContentLoaded handler
+ChangeSet #8 (2014-10-27): If color fields are added quite late like my ddmenu skinBuilder, the buildPicker cannot run onDomReady or even window.onload. So I added option cpInitOnLoad and init() function.
+ChangeSet #9 (2014-11-??): Click color chooser will toggle the display.
+*/
diff --git a/plugins/Customizer/Controller/CustomizerConfigController.php b/plugins/Customizer/Controller/CustomizerConfigController.php
new file mode 100644
index 00000000..d816384d
--- /dev/null
+++ b/plugins/Customizer/Controller/CustomizerConfigController.php
@@ -0,0 +1,388 @@
+<?php
+
+namespace Kanboard\Plugin\Customizer\Controller;
+
+require_once __DIR__.'/../vendor/autoload.php';
+
+use Kanboard\Model\ConfigModel;
+use Kanboard\Model\LanguageModel;
+use Kanboard\Controller\BaseController;
+use luizbills\CSS_Generator\Generator as CSS_Generator;
+
+/**
+ * Config Controller
+ *
+ * @package Customizer/Controller
+ * @author creecros
+ */
+class CustomizerConfigController extends BaseController
+{
+
+ /**
+ * Save settings
+ *
+ */
+ public function save()
+ {
+ if (isset($_POST['remove'])) {
+ $values = $this->request->getValues();
+ $this->remove($values['themeSelection']);
+ } else {
+
+ $values = $this->request->getValues();
+
+ if (array_key_exists('use_custom_login', $values) === false) { $this->configModel->save(['use_custom_login' => '']); }
+ if (array_key_exists('enable_cache', $values) === false) { $this->configModel->save(['enable_cache' => '']); }
+
+ if ($this->configModel->save($values)) {
+ $this->languageModel->loadCurrentLanguage();
+ $this->flash->success(t('Settings saved successfully.'));
+ } else {
+ $this->flash->failure(t('Unable to save your settings.'));
+ }
+
+ $this->response->redirect($this->helper->url->to('CustomizerFileController', 'show', array('plugin' => 'Customizer')));
+ }
+ }
+
+ /**
+ * Save user theme
+ *
+ */
+ public function usertheme()
+ {
+ $user = $this->getUser();
+ $values = $this->request->getValues();
+
+ if ($this->userMetadataModel->save($user['id'], $values)) {
+ $this->languageModel->loadCurrentLanguage();
+ $this->flash->success(t('Settings saved successfully.'));
+ } else {
+ $this->flash->failure(t('Unable to save your settings.'));
+ }
+
+ $this->response->redirect($this->helper->url->to('UserViewController', 'show', array('user_id' => $user['id'])), true);
+
+ }
+
+ /**
+ * Reset all User themes
+ *
+ */
+ public function resetUserThemes()
+ {
+ $users = $this->userModel->getAll();
+ foreach ($users as $user) {
+ $this->userMetadataModel->remove($user['id'], 'themeSelection');
+ }
+ $this->response->redirect($this->helper->url->to('CustomizerFileController', 'show', array('plugin' => 'Customizer')));
+ }
+
+ /**
+ * Toggle User themes
+ *
+ */
+ public function enableDisableThemes()
+ {
+ $status = $this->configModel->get('toggle_user_themes', 'disable');
+
+ if ($status == 'disable') { $this->configModel->save(['toggle_user_themes' => 'enable']); } else { $this->configModel->save(['toggle_user_themes' => 'disable']); }
+ $this->response->redirect($this->helper->url->to('CustomizerFileController', 'show', array('plugin' => 'Customizer')));
+ }
+
+
+ /**
+ * Upload css theme
+ *
+ */
+ public function uploadcss()
+ {
+ $target_dir = DATA_DIR . '/files/customizer/themes/';
+ $target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]);
+ $uploadOk = 1;
+ $imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION));
+ // Check if file already exists
+ if (file_exists($target_file)) {
+ $this->flash->failure(t('Sorry, file already exists.'));
+ $uploadOk = 0;
+ }
+ // Check file size
+ if ($_FILES["fileToUpload"]["size"] > 1000000) {
+ $this->flash->failure(t('Sorry, your file is too large.'));
+ $uploadOk = 0;
+ }
+ // Allow certain file formats
+ if($imageFileType != "css") {
+ $this->flash->failure(t('Sorry, only CSS files are allowed.'));
+ $uploadOk = 0;
+ }
+ // Check if $uploadOk is set to 0 by an error
+ if ($uploadOk == 0) {
+ $this->flash->failure(t('Sorry, your file was not uploaded.'));
+ // if everything is ok, try to upload file
+ } else {
+ if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
+ $this->flash->success(t('Theme file uploaded successfully.'));
+ } else {
+ $this->flash->failure(t('Sorry, there was an error uploading your file.'));
+ }
+ }
+
+ $this->response->redirect($this->helper->url->to('CustomizerFileController', 'show', array('plugin' => 'Customizer')));
+ }
+
+ public function remove($file)
+ {
+ $filename = basename($file);
+ if (file_exists(DATA_DIR . '/files/customizer/themes/' . $filename)) { unlink(DATA_DIR . '/files/customizer/themes/' . $filename); }
+ unlink($file);
+ $this->response->redirect($this->helper->url->to('CustomizerFileController', 'show', array('plugin' => 'Customizer')));
+ }
+
+ public function create_theme()
+ {
+ $values = $this->request->getValues();
+
+ $options = [
+ // default values
+ // 'indentation' => ' ', // 4 spaces
+ ];
+
+ $css = new CSS_Generator($options);
+
+ // Header
+ $css->add_rule('header',
+ [
+ 'background-color' => $values['header_background'],
+ 'background-image' => 'linear-gradient(-180deg, transparent 0%, '.$values['header_shade'].' 90%)'
+ ]
+ );
+ $css->add_rule(['header h1', 'header a .fa'],
+ [
+ 'color' => $values['header_title']
+ ]
+ );
+ $css->add_rule('a i.web-notification-icon',
+ [
+ 'color' => $values['notification_icon']
+ ]
+ );
+ // Body
+ $css->add_rule('body',
+ [
+ 'background' => $values['background_color'],
+ 'color' => $values['font_main']
+ ]
+ );
+ $css->add_rule(['ul.dropdown-submenu-open', '.accordion-title h3'],
+ [
+ 'background-color' => $values['background_color']
+ ]
+ );
+ $css->add_rule('.dropdown-submenu-open a',
+ [
+ 'color' => $values['font_main']
+ ]
+ );
+ $css->add_rule('a:hover',
+ [
+ 'color' => $values['font_main']
+ ]
+ );
+ $css->add_rule('a .fa',
+ [
+ 'color' => $values['font_main']
+ ]
+ );
+ $css->add_rule(['h1', 'h2', 'h3', '.accordion-toggle'],
+ [
+ 'color' => $values['font_main']
+ ]
+ );
+ $css->add_rule('.table-list-header a',
+ [
+ 'color' => $values['font_main']
+ ]
+ );
+ $css->add_rule('.table-list-header .table-list-header-count',
+ [
+ 'color' => $values['font_main']
+ ]
+ );
+ $css->add_rule('.table-list-row .table-list-title a',
+ [
+ 'color' => $values['font_main']
+ ]
+ );
+ $css->add_rule('.table-list-row .table-list-details strong',
+ [
+ 'color' => $values['font_main']
+ ]
+ );
+ $css->add_rule('.dropdown-menu-link-icon',
+ [
+ 'color' => $values['font_main']
+ ]
+ );
+ $css->add_rule('.page-header h2 a',
+ [
+ 'color' => $values['font_main']
+ ]
+ );
+ $css->add_rule('.sidebar>ul a:hover',
+ [
+ 'color' => $values['font_main']
+ ]
+ );
+ $css->add_rule('.sidebar>ul li.active a',
+ [
+ 'color' => $values['font_main']
+ ]
+ );
+ $css->add_rule('.task-list-icons a:hover',
+ [
+ 'color' => $values['font_main']
+ ]
+ );
+ $css->add_rule('.task-list-icons a:hover i',
+ [
+ 'color' => $values['font_main']
+ ]
+ );
+ $css->add_rule('.subtask-cell a',
+ [
+ 'color' => $values['font_main']
+ ]
+ );
+ $css->add_rule('a',
+ [
+ 'color' => $values['font_link']
+ ]
+ );
+ $css->add_rule('.table-list-category a:hover',
+ [
+ 'color' => $values['font_link']
+ ]
+ );
+ $css->add_rule(['.subtask-cell a:hover', '.subtask-cell a:focus'],
+ [
+ 'color' => $values['font_link']
+ ]
+ );
+ $css->add_rule(['a:focus', 'a:hover'],
+ [
+ 'color' => $values['font_link_focus']
+ ]
+ );
+ $css->add_rule(['.table-list-header a:hover', '.table-list-header a:focus', '.task-board a'],
+ [
+ 'color' => $values['font_secondary']
+ ]
+ );
+ $css->add_rule('.table-list-row .table-list-details',
+ [
+ 'color' => $values['font_secondary']
+ ]
+ );
+ $css->add_rule('.input-addon-field',
+ [
+ 'color' => $values['font_secondary']
+ ]
+ );
+ $css->add_rule(['.page-header h2 a:focus', '.page-header h2 a:hover', 'code'],
+ [
+ 'color' => $values['font_secondary']
+ ]
+ );
+ $css->add_rule('.sidebar>ul a',
+ [
+ 'color' => $values['font_secondary']
+ ]
+ );
+ $css->add_rule('.task-list-avatars .task-avatar-assignee',
+ [
+ 'color' => $values['font_secondary']
+ ]
+ );
+ $css->add_rule(['.task-list-icons a', '.task-list-icons span', '.task-list-icons i', '.task-date'],
+ [
+ 'color' => $values['font_secondary']
+ ]
+ );
+ $css->add_rule('span.task-date-overdue',
+ [
+ 'color' => $values['font_overdue']
+ ]
+ );
+ $css->add_rule('.sidebar>ul li.active',
+ [
+ 'border-left' => '5px solid '.$values['font_main']
+ ]
+ );
+ $css->add_rule(['.sidebar>ul li.active a:focus', '.sidebar>ul li.active a:hover'],
+ [
+ 'color' => $values['font_secondary']
+ ]
+ );
+ $css->add_rule('.sidebar>ul li:hover',
+ [
+ 'border-left' => '5px solid '.$values['font_secondary']
+ ]
+ );
+ $css->add_rule('.panel',
+ [
+ 'color' => $values['font_main']
+ ]
+ );
+ $css->add_rule(['td a.dropdown-menu strong', 'td a.dropdown-menu strong i'],
+ [
+ 'color' => $values['font_main']
+ ]
+ );
+
+ $css->add_raw('
+ #task-summary h2 {color: unset;}
+ .comments .comment:hover {background: #efefef40;}
+ .comments .comment:nth-child(even):not(.comment-highlighted):hover {background: #efefef40;}
+ .comments .comment:nth-child(even):not(.comment-highlighted) {background: #efefef40;}
+ table.table-striped tr:nth-child(odd) {background: #efefef40;}
+ header {box-shadow: 0px -1px 5px 1px;border-bottom: none;}
+ .project-header {margin-bottom: 8px;margin-top: 8px;}
+ .panel{background-color: #efefef40;border: 1px solid #efefef40;}
+ .task-board{border-width: 2px;background: #efefef22!important;}
+ div.task-board-recent {border-width: 2px;}
+ table td {border:none;}
+ table th:first-child {border-top-left-radius:8px;}
+ table th:last-child {border-top-right-radius:8px;}
+ .table-list-header{background:#efefef40;border:1px solid #efefef40;border-radius:5px 5px 0 0;line-height:28px;padding-left:3px;padding-right:3px;}
+ .table-list-row{padding-left:3px;padding-right:3px;border-bottom:1px solid #efefef40;border-right:1px solid #efefef40;}
+ .table-list-row.table-border-left{border-left:1px solid #efefef40;}
+ .table-list-row:nth-child(odd){background:#efefef30;}
+ .table-list-row:hover{background:#efefef32;border-bottom:1px solid #efefef32;border-right:1px solid #efefef32;}
+ .dropdown-menu-link-icon{text-decoration:none;}
+ .dropdown-submenu-open li{border-bottom:1px solid #efefef40;}
+ .page-header h2{margin:0;padding:0;font-weight:bold;border-bottom:1px dotted #efefef40;}
+ .sidebar>ul li{list-style-type:none;line-height:35px;border-bottom:1px dotted #efefef40;padding-left:13px;}
+ span.task-icon-age-total{border:1px solid #efefef40;padding:1px 3px 1px 3px;border-top-left-radius:3px;border-bottom-left-radius:3px;}
+ span.task-icon-age-column{border:1px solid #efefef40;border-left:none;margin-left:-5px;padding:1px 3px 1px 3px;border-top-right-radius:3px;border-bottom-right-radius:3px;}
+ .subtask-cell{padding:4px 10px;border-top:1px dotted #efefef42;border-left:1px dotted #efefef40;display:table-cell;vertical-align:middle;}
+ table th {text-align: left;padding: 0.5em 3px;border:none;background: #efefef40;}
+ .views li {white-space: nowrap;background: #efefef40;border:none;border-right: none;padding: 4px 8px;display: inline;}
+ ');
+
+
+ $minify = true;
+
+ $extension = '.css';
+ $rename = str_replace('.', '', $values['theme_name']);
+
+ if (file_exists(DATA_DIR . '/files/customizer/themes')) {
+ file_put_contents(DATA_DIR . '/files/customizer/themes/' . $rename . $extension, $css->get_output($minify));
+ } else {
+ mkdir(DATA_DIR . '/files/customizer/themes', 0755);
+ file_put_contents(DATA_DIR . '/files/customizer/themes/' . $rename . $extension, $css->get_output($minify));
+ }
+
+ $this->response->redirect($this->helper->url->to('CustomizerFileController', 'show', array('plugin' => 'Customizer')));
+ }
+}
diff --git a/plugins/Customizer/Controller/CustomizerFileController.php b/plugins/Customizer/Controller/CustomizerFileController.php
new file mode 100644
index 00000000..75271320
--- /dev/null
+++ b/plugins/Customizer/Controller/CustomizerFileController.php
@@ -0,0 +1,313 @@
+<?php
+
+namespace Kanboard\Plugin\Customizer\Controller;
+
+use Kanboard\Plugin\Customizer\Model\CustomizerFileModel;
+use Kanboard\Core\ObjectStorage\ObjectStorageException;
+use Kanboard\Controller\BaseController;
+use Kanboard\Model\ConfigModel;
+
+/**
+ * Customizer Controller
+ *
+ * @package Customizer\Controller
+ * @author creecros
+ */
+class CustomizerFileController extends BaseController
+{
+ /**
+ * Get file content from object storage
+ *
+ * @access protected
+ * @param array $file
+ * @return string
+ */
+ protected function getFileContent(array $file)
+ {
+ $content = '';
+
+ try {
+ if ($file['is_image'] == 0) {
+ $content = $this->objectStorage->get($file['path']);
+ }
+ } catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ }
+
+ return $content;
+ }
+
+ /**
+ * Output file with cache
+ *
+ * @param array $file
+ * @param $mimetype
+ */
+ protected function renderFileWithCache(array $file, $mimetype)
+ {
+ $etag = md5($file['path']);
+
+ if ($this->request->getHeader('If-None-Match') === '"'.$etag.'"') {
+ $this->response->status(304);
+ } else {
+ try {
+ $this->response->withContentType($mimetype);
+ $this->response->withCache(5 * 86400, $etag);
+ $this->response->send();
+ $this->objectStorage->output($file['path']);
+ } catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ }
+ }
+ }
+
+ /**
+ * Output file without cache
+ *
+ * @param array $file
+ * @param $mimetype
+ */
+ protected function renderFileWithoutCache(array $file, $mimetype)
+ {
+ $etag = md5($file['path']);
+
+ if ($this->request->getHeader('If-None-Match') === '"'.$etag.'"') {
+ $this->response->status(304);
+ } else {
+ try {
+ $this->response->withContentType($mimetype);
+ $this->response->withOutCache();
+ $this->response->send();
+ $this->objectStorage->output($file['path']);
+ } catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ }
+ }
+ }
+
+ public function show()
+ {
+ $logo = $this->customizerFileModel->getByType(1);
+ $flavicon = $this->customizerFileModel->getByType(2);
+ $loginlogo = $this->customizerFileModel->getByType(3);
+ $logopath = $logo['path'];
+ $flaviconpath = $flavicon['path'];
+ $this->response->html($this->helper->layout->config('customizer:file/show', array(
+ 'logo' => $logo,
+ 'title' => t('Settings').' &gt; '.t('Customizer'),
+ 'flavicon' => $flavicon,
+ 'logopath' => $logopath,
+ 'flaviconpath' => $flaviconpath,
+ 'loginlogo' => $loginlogo
+ )));
+
+ }
+
+ public function logo()
+ {
+ if ($this->logoexists()) {
+ $file = $this->customizerFileModel->getByType(1);
+ if ($this->configModel->get('enable_cache', '') == 'checked') {
+ $this->renderFileWithCache($file, $this->helper->file->getImageMimeType($file['name']));
+ } else {
+ $this->renderFileWithoutCache($file, $this->helper->file->getImageMimeType($file['name']));
+ }
+ }
+ }
+
+ public function logo_setting()
+ {
+ if ($this->logoexists()) {
+ $file = $this->customizerFileModel->getByType(1);
+ $this->renderFileWithoutCache($file, $this->helper->file->getImageMimeType($file['name']));
+ }
+ }
+
+ public function loginlogo()
+ {
+ if ($this->loginlogoexists()) {
+ $file = $this->customizerFileModel->getByType(3);
+ if ($this->configModel->get('enable_cache', '') == 'checked') {
+ $this->renderFileWithCache($file, $this->helper->file->getImageMimeType($file['name']));
+ } else {
+ $this->renderFileWithoutCache($file, $this->helper->file->getImageMimeType($file['name']));
+ }
+ }
+ }
+
+ public function loginlogo_setting()
+ {
+ if ($this->loginlogoexists()) {
+ $file = $this->customizerFileModel->getByType(3);
+ $this->renderFileWithoutCache($file, $this->helper->file->getImageMimeType($file['name']));
+ }
+ }
+
+ public function icon()
+ {
+ if ($this->iconexists()) {
+ $file = $this->customizerFileModel->getByType(2);
+ if ($this->configModel->get('enable_cache', '') == 'checked') {
+ $this->renderFileWithCache($file, $this->helper->file->getImageMimeType($file['name']));
+ } else {
+ $this->renderFileWithoutCache($file, $this->helper->file->getImageMimeType($file['name']));
+ }
+ }
+ }
+
+ public function icon_setting()
+ {
+ if ($this->iconexists()) {
+ $file = $this->customizerFileModel->getByType(2);
+ $this->renderFileWithoutCache($file, $this->helper->file->getImageMimeType($file['name']));
+ }
+ }
+
+ public function link()
+ {
+ if ($this->logoexists() && $this->linkexists()) {
+ return $this->response->redirect($this->configModel->get('login_link', 'https://kanboard.org'));
+ } else {
+ return $this->response->redirect($this->configModel->get('application_url', '') . 'login');
+ }
+ }
+
+ public function logoexists()
+ {
+ if (null !== $this->customizerFileModel->getByType(1)) { return true; } else { return false; }
+ }
+
+ public function loginlogoexists()
+ {
+ if (null !== $this->customizerFileModel->getByType(3)) {
+ $customizer['loginCheck'] = true;
+ return true;
+ } else {
+ $customizer['loginCheck'] = false;
+ return false;
+ }
+ }
+
+ public function linkexists()
+ {
+ if ($this->configModel->exists('login_link')) { return true; } else { return false; }
+ }
+
+ public function iconexists()
+ {
+ if (null !== $this->customizerFileModel->getByType(2)) { return true; } else { return false; }
+ }
+
+ public function image()
+ {
+ $file = $this->customizerFileModel->getById($this->request->getIntegerParam('file_id'));
+ if ($this->configModel->get('enable_cache', '') == 'checked') {
+ $this->renderFileWithCache($file, $this->helper->file->getImageMimeType($file['name']));
+ } else {
+ $this->renderFileWithoutCache($file, $this->helper->file->getImageMimeType($file['name']));
+ }
+ }
+
+ /**
+ * File upload form
+ *
+ * @access public
+ */
+ public function create()
+ {
+ ini_set('display_errors', 1);
+ ini_set('display_startup_errors', 1);
+ error_reporting(E_ALL);
+ $custom_id = $this->request->getIntegerParam('custom_id');
+ if ($custom_id == 1) {
+ $this->response->html($this->template->render('customizer:file/upload_logo', array(
+ 'custom_id' => $custom_id,
+ 'multiple' => false,
+ )));
+ } else if ($custom_id == 2) {
+ $this->response->html($this->template->render('customizer:file/upload_flavicon', array(
+ 'custom_id' => $custom_id,
+ 'multiple' => false,
+ )));
+ } else if ($custom_id == 3) {
+ $this->response->html($this->template->render('customizer:file/upload_loginlogo', array(
+ 'custom_id' => $custom_id,
+ 'multiple' => false,
+ )));
+ }
+ }
+
+ /**
+ * File upload (save files)
+ *
+ * @access public
+ */
+ public function save()
+ {
+ ini_set('display_errors', 1);
+ ini_set('display_startup_errors', 1);
+ error_reporting(E_ALL);
+ $custom_id = $this->request->getIntegerParam('custom_id');
+ if ($custom_id ==3) { $loginCheck = true; }
+
+ $result = $this->customizerFileModel->uploadFiles($custom_id, $this->request->getFileInfo('files'));
+ if ($this->request->isAjax()) {
+ if (!$result) {
+ $this->response->json(array('message' => t('Unable to upload files, check the permissions of your data folder.')), 500);
+ } else {
+ $this->response->json(array('message' => 'OK'));
+ }
+ } else {
+ if (!$result) {
+ $this->flash->failure(t('Unable to upload files, check the permissions of your data folder.'));
+ }
+ }
+ }
+ /**
+ * Remove a file
+ *
+ * @access public
+ */
+ public function remove()
+ {
+ $this->checkCSRFParam();
+ $custom_id = $this->request->getIntegerParam('custom_id');
+ $file = $this->customizerFileModel->getById($this->request->getIntegerParam('file_id'));
+ if ($this->customizerFileModel->remove($file['id'])) {
+ $this->flash->success(t('File removed successfully.'));
+ } else {
+ $this->flash->failure(t('Unable to remove this file.'));
+ }
+
+ }
+
+ public function removeform()
+ {
+ $this->checkCSRFParam();
+ $custom_id = $this->request->getIntegerParam('custom_id');
+ if ($custom_id == 3) { $loginCheck = false; }
+ $file = $this->customizerFileModel->getById($this->request->getIntegerParam('file_id'));
+ if ($this->customizerFileModel->remove($file['id'])) {
+ $this->flash->success(t('File removed successfully.'));
+ } else {
+ $this->flash->failure(t('Unable to remove this file.'));
+ }
+
+ return $this->response->redirect($this->configModel->get('application_url', '') . 'settings/customizer');
+
+ }
+ /**
+ * Confirmation dialog before removing a file
+ *
+ * @access public
+ */
+ public function confirm()
+ {
+ $custom_id = $this->request->getIntegerParam('custom_id');
+ $file = $this->customizerFileModel->getById($this->request->getIntegerParam('file_id'));
+ $this->response->html($this->template->render('customizer:file/remove', array(
+ 'custom_id' => $custom_id,
+ 'file' => $file,
+ )));
+ }
+}
diff --git a/plugins/Customizer/Helper/DynamicAvatar.php b/plugins/Customizer/Helper/DynamicAvatar.php
new file mode 100644
index 00000000..cf7ab83f
--- /dev/null
+++ b/plugins/Customizer/Helper/DynamicAvatar.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Kanboard\Plugin\Customizer\Helper;
+
+use Kanboard\Helper\AvatarHelper;
+use Kanboard\Core\Base;
+/**
+ * Avatar Helper
+ *
+ * @package helper
+ * @author Craig Crosby
+ */
+class DynamicAvatar extends AvatarHelper
+{
+
+ public function dynamicRender($user_id, $username, $name, $email, $avatar_path, $css = 'avatar-left', $size = 48)
+ {
+ if (empty($user_id) && empty($username)) {
+ $html = $this->avatarManager->renderDefault($size);
+ } else {
+ $html = $this->avatarManager->render($user_id, $username, $name, $email, $avatar_path, $size);
+ }
+ return '<div id="'.$css.'" class="avatar avatar-dyn '.$css.'">'.$html.'</div>';
+ }
+
+ public function dynamic($user_id, $username, $name, $email, $avatar_path, $css = '', $size)
+ {
+ return $this->dynamicRender($user_id, $username, $name, $email, $avatar_path, $css, $size);
+ }
+
+ public function currentUserDynamic($css = '')
+ {
+ $user = $this->userSession->getAll();
+ return $this->dynamic($user['id'], $user['username'], $user['name'], $user['email'], $user['avatar_path'], $css, $this->configModel->get('av_size', '20'));
+ }
+
+ public function boardDynamicRender($user_id, $username, $name, $email, $avatar_path, $css = 'avatar-left', $size = 48)
+ {
+ if (empty($user_id) && empty($username)) {
+ $html = $this->avatarManager->renderDefault($size);
+ } else {
+ $html = $this->avatarManager->render($user_id, $username, $name, $email, $avatar_path, $size);
+ }
+ return '<div id="'.$css.'" class="avatar avatar-bdyn '.$css.'">'.$html.'</div>';
+ }
+
+ public function boardDynamic($user_id, $username, $name, $email, $avatar_path, $css = '', $size)
+ {
+ return $this->boardDynamicRender($user_id, $username, $name, $email, $avatar_path, $css, $size);
+ }
+
+ public function boardCurrentUserDynamic($css = '')
+ {
+ $user = $this->userSession->getAll();
+ return $this->boardDynamic($user['id'], $user['username'], $user['name'], $user['email'], $user['avatar_path'], $css, $this->configModel->get('b_av_size', '20'));
+ }
+
+
+ }
diff --git a/plugins/Customizer/Helper/ThemeHelper.php b/plugins/Customizer/Helper/ThemeHelper.php
new file mode 100644
index 00000000..c2f767d1
--- /dev/null
+++ b/plugins/Customizer/Helper/ThemeHelper.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Kanboard\Plugin\Customizer\Helper;
+
+use Kanboard\Core\Base;
+
+class ThemeHelper extends Base
+{
+
+ public function reverseSelect($name, array $options, array $values = array(), array $errors = array(), array $attributes = array(), $class = '')
+ {
+ $html = '<select name="'.$name.'" id="form-'.$name.'" class="'.$class.'" '.implode(' ', $attributes).'>';
+ foreach ($options as $id => $value) {
+ $html .= '<option value="'.$this->helper->text->e($value).'"';
+ if (isset($values->$name) && $value == $values->$name) {
+ $html .= ' selected="selected"';
+ }
+ if (isset($values[$name]) && $value == $values[$name]) {
+ $html .= ' selected="selected"';
+ }
+ $html .= '>'.$this->helper->text->e($id).'</option>';
+ }
+ $html .= '</select>';
+ $html .= $this->errorList($errors, $name);
+ return $html;
+ }
+
+ public function reverseSelectOnChange($name, array $options, array $values = array(), array $errors = array(), array $attributes = array(), $class = '')
+ {
+ $html = '<select name="'.$name.'" id="userthemeSelection" class="'.$class.'" '.implode(' ', $attributes).'>';
+ foreach ($options as $id => $value) {
+ $html .= '<option value="'.$this->helper->text->e($value).'"';
+ if (isset($values->$name) && $value == $values->$name) {
+ $html .= ' selected="selected"';
+ }
+ if (isset($values[$name]) && $value == $values[$name]) {
+ $html .= ' selected="selected"';
+ }
+ $html .= '>'.$this->helper->text->e($id).'</option>';
+ }
+ $html .= '</select>';
+ $html .= $this->errorList($errors, $name);
+ return $html;
+ }
+
+ private function errorClass(array $errors, $name)
+ {
+ return ! isset($errors[$name]) ? '' : ' form-error';
+ }
+
+ private function errorList(array $errors, $name)
+ {
+ $html = '';
+ if (isset($errors[$name])) {
+ $html .= '<ul class="form-errors">';
+ foreach ($errors[$name] as $error) {
+ $html .= '<li>'.$this->helper->text->e($error).'</li>';
+ }
+ $html .= '</ul>';
+ }
+ return $html;
+ }
+
+ }
diff --git a/plugins/Customizer/LICENSE b/plugins/Customizer/LICENSE
new file mode 100644
index 00000000..7c5e911a
--- /dev/null
+++ b/plugins/Customizer/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Craig Crosby
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/plugins/Customizer/Locale/it_IT/translations.php b/plugins/Customizer/Locale/it_IT/translations.php
new file mode 100644
index 00000000..aea56cfc
--- /dev/null
+++ b/plugins/Customizer/Locale/it_IT/translations.php
@@ -0,0 +1,49 @@
+<?php
+
+return [
+ 'Image Assets & Settings' => 'Risorse e impostazioni dell\'immagini',
+ 'Header Image' => 'Logo dell\'intestazione',
+ 'Login Image' => 'Logo di accesso',
+ 'Upload Header Logo' => 'Carica il logo dell\'intestazione',
+ 'Header Logo' => 'Logo intestazione',
+ 'Recommend 100 pixels in width, *.png, *.jpg, *.gif, max size 500kb.' => 'Dimensione consigliata in larghezza 100 pixel, formati: * .png, * .jpg, * .gif, dimensione massima 500kb.',
+ 'Drag and drop your file here' => 'Trascina e rilascia il tuo file qui',
+ 'or' => 'o',
+ 'choose file' => 'scegli il file',
+ 'Recommend 50x50 pixels, *.png only, max size 20kb.' => 'Dimensione consigliata 50x50 pixel, formato consentito * .png, dimensione massima 20kb.',
+ 'Remove Header Logo' => 'Rimuovi il logo dell\'intestazione',
+ 'Upload Login Logo' => 'Carica il logo di accesso',
+ 'Remove Login Logo' => 'Rimuovi il logo di accesso',
+ 'Favicon Image' => 'Immagine favicon',
+ 'Upload Favicon' => 'Carica favicon',
+ 'Remove Favicon' => 'Rimuovi favicon',
+ 'Links & Settings' => 'Collegamenti e impostazioni',
+ 'Login Link' => 'Link di accesso',
+ 'Example: <code>https://example.kanboard.org/</code> (used as logo link on login page)' => 'Esempio: <code> https://example.kanboard.org </code> (utilizzato come collegamento logo sulla pagina di accesso)',
+ 'Login Page Background Color' => 'Colore di sfondo della pagina di accesso',
+ 'Login Panel Shadow Color' => 'Colore dell\'ombra del pannello di accesso',
+ 'Login Panel Border Color' => 'Colore del bordo del pannello di accesso',
+ 'Login Panel Border Thickness' => 'Spessore del bordo del pannello di accesso',
+ 'Login Panel Color' => 'Colore del pannello di accesso',
+ 'Login Panel Shadow Intensity' => 'Intensità dell\'ombra del pannello di accesso',
+ 'Login Button Background Color' => 'Colore di sfondo del pulsante di accesso',
+ 'Login Button Shadow Color' => 'Colore dell\'ombra del pulsante di accesso',
+ 'Login Button Border Color' => 'Colore del bordo del pulsante di accesso',
+ 'Login Button Shade Color' => 'Colore sfumato del pulsante di accesso',
+ 'Login Button Font Color' => 'Colore del carattere del pulsante di accesso',
+ 'Login Button Shadow Intensity' => 'Intensità ombra del pulsante di accesso',
+ 'Login Button Border Thickness' => 'Spessore bordo del pulsante di accesso',
+ 'Login Button Width' => 'Larghezza del pulsante di accesso',
+ 'Preview' => 'Anteprima',
+ 'Login Background Image URL' => 'URL dell\'immagine di sfondo di accesso',
+ 'Header Logo Size' => 'Dimensioni del logo dell\'intestazione',
+ 'Example: <code>https://source.unsplash.com/random</code> (URL for a background image on the login page, centered, autoscale, no-repeat)' => 'Esempio: <codice> https://source.unsplash.com/random </ code> (URL per un\'immagine di sfondo nella pagina di accesso, centrata, automatica, senza ripetizione)',
+ 'Login Logo Size' => 'Dimensione logo di accesso',
+ 'Example: <code>30</code> (Default is 30px in height, intgers only, max 999)' => 'Esempio: <code> 30 </code> (l\'impostazione predefinita è 30px in altezza, solo numeri interi, max 999)',
+ 'Example: <code>50</code> (Default is 50px in height, intgers only, max 999)' => 'Esempio: <code> 50 </code> (l\'impostazione predefinita è 50px in altezza, solo numeri interi, max 999)',
+ 'Theme' => 'Tema',
+ 'You can see a preview only after saving' => 'Puoi vedere l\'anteprima solo dopo aver salvato',
+ 'Login Page Settings' => 'Impostazioni della pagina di accesso',
+ '&nbsp;pixels high' => '&nbsp;altezza pixel',
+ 'Use Custom Login Settings' => 'Usa le impostazioni di accesso personalizzate'
+];
diff --git a/plugins/Customizer/Model/CustomizerFileModel.php b/plugins/Customizer/Model/CustomizerFileModel.php
new file mode 100644
index 00000000..64dbc2c8
--- /dev/null
+++ b/plugins/Customizer/Model/CustomizerFileModel.php
@@ -0,0 +1,320 @@
+<?php
+
+namespace Kanboard\Plugin\Customizer\Model;
+
+use Exception;
+use Kanboard\Core\Base;
+use Kanboard\Core\Thumbnail;
+use Kanboard\Core\ObjectStorage\ObjectStorageException;
+
+/**
+ * Customizer File Model
+ *
+ * @package Customizer\Model
+ * @author creecros
+ */
+class CustomizerFileModel extends Base
+{
+ /**
+ * Table name
+ *
+ * @var string
+ */
+ const TABLE = 'customizer_files';
+
+ /**
+ * Get the table
+ *
+ * @abstract
+ * @access protected
+ * @return string
+ */
+ public function getTable()
+ {
+ return self::TABLE;
+ }
+
+ /**
+ * Define the path prefix
+ *
+ * @abstract
+ * @access protected
+ * @return string
+ */
+ public function getPathPrefix()
+ {
+ return 'customizer';
+ }
+
+ /**
+ * Get a file by the Id
+ *
+ * @access public
+ * @param integer $file_id File id
+ * @return array
+ */
+ public function getById($file_id)
+ {
+ return $this->db->table($this->getTable())->eq('id', $file_id)->findOne();
+ }
+
+ /**
+ * Get a file by the type
+ *
+ * @access public
+ * @param integer $custom_id 1=logo 2=flavicon 3=loginlogo
+ * @return array
+ */
+ public function getByType($custom_id)
+ {
+ return $this->db->table($this->getTable())->eq('custom_id', $custom_id)->findOne();
+ }
+
+ /**
+ * Get a file id by the type
+ *
+ * @access public
+ * @param integer $custom_id 1=logo 2=flavicon 3=loginlogo
+ * @return array
+ */
+ public function getIdByType($custom_id)
+ {
+ $file = $this->db->table($this->getTable())->eq('custom_id', $custom_id)->findOne();
+ return $file['id'];
+ }
+
+ /**
+ * Get all files by the type
+ *
+ * @access public
+ * @param integer $custom_id 1=logo 2=flavicon 3=loginlogo
+ * @return array
+ */
+ public function getAllByType($custom_id)
+ {
+ return $this->db->table($this->getTable())->eq('custom_id', $custom_id)->findAll();
+ }
+
+ /**
+ * Create a file entry in the database
+ *
+ * @access public
+ * @param integer $custom_id 1=logo 2=flavicon
+ * @param string $name Filename
+ * @param string $path Path on the disk
+ * @param integer $size File size
+ * @return bool|integer
+ */
+ public function create($custom_id, $name, $path, $size)
+ {
+ $values = array(
+ 'custom_id' => $custom_id,
+ 'name' => substr($name, 0, 255),
+ 'path' => $path,
+ 'is_image' => $this->isImage($name) ? 1 : 0,
+ 'size' => $size,
+ 'user_id' => $this->userSession->getId() ?: 0,
+ 'date' => time(),
+ );
+ if (null !== $this->getByType($custom_id)) {
+ foreach ($this->getAllByType($custom_id) as $image) { $this->remove($image['id']); }
+ $result = $this->db->table($this->getTable())->insert($values);
+ } else {
+ $result = $this->db->table($this->getTable())->insert($values);
+ }
+ if ($result) {
+ $file_id = (int) $this->db->getLastId();
+ return $file_id;
+ }
+ return false;
+ }
+
+ /**
+ * Remove a file
+ *
+ * @access public
+ * @param integer $file_id File id
+ * @return bool
+ */
+ public function remove($file_id)
+ {
+ try {
+ $file = $this->getById($file_id);
+ $this->objectStorage->remove($file['path']);
+ if ($file['is_image'] == 1) {
+ $this->objectStorage->remove($this->getThumbnailPath($file['path']));
+ }
+ return $this->db->table($this->getTable())->eq('id', $file['id'])->remove();
+ } catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ return false;
+ }
+ }
+
+ /**
+ * Check if a filename is an image (file types that can be shown as thumbnail)
+ *
+ * @access public
+ * @param string $filename Filename
+ * @return bool
+ */
+ public function isImage($filename)
+ {
+ switch (get_file_extension($filename)) {
+ case 'jpeg':
+ case 'jpg':
+ case 'png':
+ case 'gif':
+ return true;
+ }
+ return false;
+ }
+ /**
+ * Generate the path for a thumbnails
+ *
+ * @access public
+ * @param string $key Storage key
+ * @return string
+ */
+ public function getThumbnailPath($key)
+ {
+ return 'thumbnails'.DIRECTORY_SEPARATOR.$key;
+ }
+
+ /**
+ * Generate the path for a new filename
+ *
+ * @access public
+ * @param integer $id Foreign key
+ * @param string $filename Filename
+ * @return string
+ */
+ public function generatePath($id, $filename)
+ {
+ return $this->getPathPrefix().DIRECTORY_SEPARATOR.$id.DIRECTORY_SEPARATOR.hash('sha1', $filename.time());
+ }
+
+ /**
+ * Upload multiple files
+ *
+ * @access public
+ * @param integer $id
+ * @param array $files
+ * @return bool
+ */
+ public function uploadFiles($id, array $files)
+ {
+ try {
+ if (empty($files)) {
+ return false;
+ }
+ foreach (array_keys($files['error']) as $key) {
+ $file = array(
+ 'name' => $files['name'][$key],
+ 'tmp_name' => $files['tmp_name'][$key],
+ 'size' => $files['size'][$key],
+ 'error' => $files['error'][$key],
+ );
+ $this->uploadFile($id, $file);
+ }
+ return true;
+ } catch (Exception $e) {
+ $this->logger->error($e->getMessage());
+ return false;
+ }
+ }
+
+ /**
+ * Upload a file
+ *
+ * @access public
+ * @param integer $id
+ * @param array $file
+ * @throws Exception
+ */
+ public function uploadFile($id, array $file)
+ {
+ if ($file['error'] == UPLOAD_ERR_OK && $file['size'] > 0) {
+ $destination_filename = $this->generatePath($id, $file['name']);
+ if ($this->isImage($file['name'])) {
+ $this->generateThumbnailFromFile($file['tmp_name'], $destination_filename);
+ }
+ $this->objectStorage->moveUploadedFile($file['tmp_name'], $destination_filename);
+ $this->create($id, $file['name'], $destination_filename, $file['size']);
+ } else {
+ throw new Exception('File not uploaded: '.var_export($file['error'], true));
+ }
+ }
+
+ /**
+ * Handle file upload (base64 encoded content)
+ *
+ * @access public
+ * @param integer $id
+ * @param string $originalFilename
+ * @param string $data
+ * @param bool $isEncoded
+ * @return bool|int
+ */
+ public function uploadContent($id, $originalFilename, $data, $isEncoded = true)
+ {
+ try {
+ if ($isEncoded) {
+ $data = base64_decode($data);
+ }
+ if (empty($data)) {
+ $this->logger->error(__METHOD__.': Content upload with no data');
+ return false;
+ }
+ $destinationFilename = $this->generatePath($id, $originalFilename);
+ $this->objectStorage->put($destinationFilename, $data);
+ if ($this->isImage($originalFilename)) {
+ $this->generateThumbnailFromData($destinationFilename, $data);
+ }
+ return $this->create(
+ $id,
+ $originalFilename,
+ $destinationFilename,
+ strlen($data)
+ );
+ } catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ return false;
+ }
+ }
+
+ /**
+ * Generate thumbnail from a blob
+ *
+ * @access public
+ * @param string $destination_filename
+ * @param string $data
+ */
+ public function generateThumbnailFromData($destination_filename, &$data)
+ {
+ $blob = Thumbnail::createFromString($data)
+ ->resize()
+ ->toString();
+ $this->objectStorage->put($this->getThumbnailPath($destination_filename), $blob);
+ }
+ /**
+ * Generate thumbnail from a local file
+ *
+ * @access public
+ * @param string $uploaded_filename
+ * @param string $destination_filename
+ */
+ public function generateThumbnailFromFile($uploaded_filename, $destination_filename)
+ {
+ $blob = Thumbnail::createFromFile($uploaded_filename)
+ ->resize()
+ ->toString();
+ $this->objectStorage->put($this->getThumbnailPath($destination_filename), $blob);
+ }
+
+ public function getUserSessionId()
+ {
+ return $this->userSession->getId();
+ }
+
+}
diff --git a/plugins/Customizer/Plugin.php b/plugins/Customizer/Plugin.php
new file mode 100644
index 00000000..4055d4a4
--- /dev/null
+++ b/plugins/Customizer/Plugin.php
@@ -0,0 +1,173 @@
+<?php
+
+namespace Kanboard\Plugin\Customizer;
+
+use Kanboard\Core\Plugin\Base;
+use Kanboard\Core\Translator;
+use Kanboard\Core\Security\Role;
+use Kanboard\Event\AuthSuccessEvent;
+use Kanboard\Core\Security\AuthenticationManager;
+use Kanboard\Plugin\Customizer\Model\CustomizerFileModel;
+
+class Plugin extends Base
+{
+
+ public function initialize()
+ {
+ global $customizer;
+
+ // Themes
+ $customizer['themes'] = array(
+ 'Default' => 'plugins/Customizer/Assets/css/theme.css'
+ );
+
+ $scanned_temp_themes = array_diff(scandir('plugins/Customizer/Assets/css/userthemes'), array('..', '.'));
+ $scanned_preset_themes = array_diff(scandir('plugins/Customizer/Assets/css/themes'), array('..', '.'));
+
+ foreach ($scanned_temp_themes as $theme) {
+ unlink('plugins/Customizer/Assets/css/userthemes/' . $theme);
+ }
+
+ if (file_exists(DATA_DIR . '/files/customizer/themes')) {
+ $scanned_user_themes = array_diff(scandir(DATA_DIR . '/files/customizer/themes'), array('..', '.'));
+ foreach ($scanned_user_themes as $theme) {
+ copy(DATA_DIR . '/files/customizer/themes/' . $theme, 'plugins/Customizer/Assets/css/userthemes/' . $theme);
+ $customizer['themes'][rtrim($theme, '.css')] = 'plugins/Customizer/Assets/css/userthemes/' . $theme;
+ }
+ } else { mkdir(DATA_DIR . '/files/customizer/themes', 0755, true); }
+
+ foreach ($scanned_preset_themes as $theme) {
+ $customizer['themes'][rtrim($theme, '.css')] = 'plugins/Customizer/Assets/css/themes/' . $theme;
+ }
+
+
+
+ //Helper
+ $this->helper->register('themeHelper', '\Kanboard\Plugin\Customizer\Helper\ThemeHelper');
+ $this->helper->register('dynamicAvatar', '\Kanboard\Plugin\Customizer\Helper\DynamicAvatar');
+
+ //Check if login logo is set
+ if (null !== $this->customizerFileModel->getByType(3)) {
+ $customizer['loginCheck'] = true;
+ } else {
+ $customizer['loginCheck'] = false;
+ }
+
+ //Grabs login page settings from database
+ $customizer['backURL'] = $this->configModel->get('background_url', '');
+ $customizer['backColor'] = $this->configModel->get('loginbackground_color', '#ffffff');
+ $customizer['logoSize'] = $this->configModel->get('loginlogo_size', '50');
+ $customizer['loginpanel_color'] = $this->configModel->get('loginpanel_color', '#ffffff');
+ $customizer['login_shadow_color'] = $this->configModel->get('login_shadow_color', '#333');
+ $customizer['login_shadow'] = $this->configModel->get('login_shadow', '0');
+ $customizer['login_border_color'] = $this->configModel->get('login_border_color', '#ffffff');
+ $customizer['login_border'] = $this->configModel->get('login_border', '0');
+ $customizer['login_btn_color'] = $this->configModel->get('login_btn_color', '#3079ed');
+ $customizer['login_btn_shadow_color'] = $this->configModel->get('login_btn_shadow_color', '#333');
+ $customizer['login_btn_border_color'] = $this->configModel->get('login_btn_border_color', 'transparent');
+ $customizer['login_btn_shade_color'] = $this->configModel->get('login_btn_shade_color', 'transparent');
+ $customizer['login_btn_font_color'] = $this->configModel->get('login_btn_font_color', '#ffffff');
+ $customizer['login_btn_shadow'] = $this->configModel->get('login_btn_shadow', '0');
+ $customizer['login_btn_border'] = $this->configModel->get('login_btn_border', '0');
+ $customizer['login_btn_width'] = $this->configModel->get('login_btn_width', '95');
+ $customizer['login_note'] = $this->configModel->get('login_note', '');
+
+ //Templates and Assets
+ $this->template->hook->attach('template:config:sidebar', 'customizer:config/sidebar');
+ $this->template->setTemplateOverride('header/title', 'customizer:header/title');
+ $this->template->setTemplateOverride('header/user_dropdown', 'customizer:header/user_dropdown');
+ $this->template->setTemplateOverride('board/task_avatar', 'customizer:board/task_avatar');
+ $this->template->setTemplateOverride('layout', 'customizer:layout/layout');
+ $this->template->setTemplateOverride('auth/index', 'customizer:layout/index');
+ $this->hook->on('template:layout:css', array('template' => 'plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.css'));
+ $this->hook->on('template:layout:js', array('template' => 'plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.js'));
+ $this->hook->on('template:layout:css', array('template' => 'plugins/Customizer/Assets/css/customizer.css'));
+ $this->hook->on('template:layout:js', array('template' => 'plugins/Customizer/Assets/js/customizer.js'));
+ $this->template->hook->attach('customizer:config:themecreator', 'customizer:config/themecreator');
+
+ if ($customizer['login_note'] != '') {
+ $this->template->hook->attach('template:auth:login-form:newbox', 'customizer:layout/note');
+ }
+ if ($this->configModel->get('toggle_user_themes', 'disable') == 'enable') {
+ $this->template->setTemplateOverride('user_modification/show', 'customizer:user_mod/show');
+ }
+
+
+ if ($this->configModel->get('use_custom_login', '') == 'checked') {
+ $this->template->hook->attach('customizer:config:style', 'customizer:layout/preview_style');
+ $this->template->hook->attach('template:auth:login-form:before', 'customizer:layout/login_with_custom');
+ } else {
+ $this->template->hook->attach('template:auth:login-form:before', 'customizer:layout/login_no_custom');
+ }
+
+ //Routes
+ $this->route->addRoute('settings/customizer', 'CustomizerFileController', 'show', 'Customizer');
+
+ //Permissions for login page to access logos
+ $this->applicationAccessMap->add('CustomizerFileController', array('image', 'loginlogo', 'logo', 'link', 'logoexists', 'linkexists'), Role::APP_PUBLIC);
+
+ //Get accurate version
+ $wasmaster = str_replace('v', '', APP_VERSION);
+ $wasmaster = preg_replace('/\s+/', '', $wasmaster);
+
+ if (strpos(APP_VERSION, 'master') !== false && file_exists('ChangeLog')) { $wasmaster = trim(file_get_contents('ChangeLog', false, null, 8, 6), ' '); }
+ if (version_compare($wasmaster, '1.2.4') >= 0) {
+ $this->template->setTemplateOverride('header/title', 'customizer:header/title');
+ } else {
+ $this->template->setTemplateOverride('header/title', 'customizer:header/title_older_kb');
+ }
+
+ }
+
+ public function onStartup()
+ {
+ Translator::load($this->languageModel->getCurrentLanguage(), __DIR__.'/Locale');
+ $user_id = $this->customizerFileModel->getUserSessionId();
+ $user_theme = $this->userMetadataModel->get($user_id, 'themeSelection', $this->configModel->get('themeSelection', 'plugins/Customizer/Assets/css/theme.css' ));
+ $default_theme = $this->configModel->get('themeSelection', 'plugins/Customizer/Assets/css/theme.css');
+ if ($this->configModel->get('toggle_user_themes', 'disable') == 'enable') {
+ $this->hook->on('template:layout:css', array('template' => $user_theme));
+ } else {
+ $this->hook->on('template:layout:css', array('template' => $default_theme));
+ }
+ }
+
+ public function getClasses() {
+ return array(
+ 'Plugin\Customizer\Model' => array(
+ 'CustomizerFileModel',
+ )
+ );
+ }
+
+ public function getPluginName()
+ {
+ return 'Customizer';
+ }
+
+ public function getPluginDescription()
+ {
+ return t('Completely customize your Kanboard experience with logos, favicons & themes.');
+ }
+
+ public function getPluginAuthor()
+ {
+ return 'Craig Crosby';
+ }
+
+ public function getPluginVersion()
+ {
+ return '1.13.1';
+ }
+
+ public function getPluginHomepage()
+ {
+ return 'https://github.com/creecros/Customizer';
+ }
+
+ public function getCompatibleVersion()
+ {
+ return '>=1.0.42';
+ }
+}
+
diff --git a/plugins/Customizer/README.md b/plugins/Customizer/README.md
new file mode 100644
index 00000000..9ae17cdb
--- /dev/null
+++ b/plugins/Customizer/README.md
@@ -0,0 +1,142 @@
+## Checkout our latest project
+[![](https://raw.githubusercontent.com/docpht/docpht/master/public/assets/img/logo.png)](https://github.com/docpht/docpht)
+
+- With [DocPHT](https://github.com/docpht/docpht) you can take notes and quickly document anything and without the use of any database.
+-----------
+[![Latest release](https://img.shields.io/github/release/creecros/Customizer.svg)](https://github.com/creecros/Customizer/releases)
+[![GitHub license](https://img.shields.io/github/license/Naereen/StrapDown.js.svg)](https://github.com/creecros/Customizer/blob/master/LICENSE)
+[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/creecros/Customizer/graphs/contributors)
+[![Open Source Love](https://badges.frapsoft.com/os/v1/open-source.svg?v=103)]()
+[![Downloads](https://img.shields.io/github/downloads/creecros/Customizer/total.svg)](https://github.com/creecros/Customizer/releases)
+
+Donate to help keep this project maintained.
+<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SEGNEVQFXHXGW&source=url">
+<img src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif" border="0" name="submit" title="PayPal - The safer, easier way to pay online!" alt="Donate with PayPal button" /></a>
+
+**:star: If you use it, you should star it on Github!**
+*It's the least you can do for all the work put into it!*
+
+Customizer - it's like magic!
+----------
+
+## Author: [creecros](https://github.com/creecros)
+### Other [Contributors](https://github.com/creecros/Customizer/graphs/contributors)
+
+If you need a Logo and Favicon, try using this simple logo generator: https://creecros.github.io/simple_logo_gen/
+
+:sparkles: **Adds a settings panel Settings>Customizer:**
+
+![image](https://user-images.githubusercontent.com/26339368/49309828-7fc47800-f4aa-11e8-809e-8cc9686a2a2d.png)
+
+**GUI to customize...**
+
+* [header logo](#header-logo-rainbow)
+ * set header logo size
+* [upper right corner Avatar Icon](#header-avatar-icon-boy)
+ * set icon size
+ * set border radius
+* [task board Avatar Icon](#task-board-avatar-icon-girl)
+ * set icon size
+ * set border radius
+* [customizable login screen](#fully-customizable-login-screen-gem)
+ * [login logo](#login-logo-peach)
+ * [login page background image url](#login-screen-background-image-dart)
+ * login logo link
+ * set login logo size
+ * set login page background color
+ * set login panel color
+ * panel shadow adjustment (no shadow to heavy shadow with color adjustment)
+ * panel border adjustment (no border to thick border with color adjustment)
+ * add a [custom note panel](#login-screen-custom-note-book) on login screen
+* [favicon](#favicon-beginner)
+* theme management
+ * can upload css files to add to theme selection
+ * can remove themes from interface
+ * can enable/disable user themes (i.e. one global theme for everyone, or users get to select their own theme)
+ * admins can reset all users themes to default
+* [theme selector](#includes-preset-themes-mega) with 7 preloaded themes
+ * Github :octocat:
+ * Galaxy :milky_way:
+ * Breathe :partly_sunny:
+ * Clemson <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/Clemson_Tigers_logo.svg/2000px-Clemson_Tigers_logo.svg.png" height="20">
+ * Blueboard :blue_book:
+ * Material :sparkles:
+ * KindaDark :8ball:
+* [theme creator](#theme-creator-notes)
+
+
+**...to your site without any backend coding or config settings.**
+
+**Want more customization? Take a look at [BlueTek](https://github.com/BlueTeck)'s [BoardCustomizer](https://github.com/BlueTeck/kanboard_plugin_boardcustomizer) plugin**
+
+## Favicon :beginner:
+
+![image](https://user-images.githubusercontent.com/26339368/47174055-a43f0900-d2dd-11e8-9932-430e11b74fea.png)
+
+## Header Logo :rainbow:
+
+![image](https://user-images.githubusercontent.com/26339368/47369113-f9e62d80-d6b0-11e8-90e0-974c31b4b535.png)
+
+## Header Avatar Icon :boy:
+
+![image](https://user-images.githubusercontent.com/26339368/55774303-8bc8d380-5a62-11e9-8d59-6dc2a1c33387.png)
+
+## Task Board Avatar Icon :girl:
+
+![image](https://user-images.githubusercontent.com/26339368/59513842-ae73d000-8e89-11e9-94b5-ae27a0f8c651.png)
+
+## Login Logo :peach:
+
+![image](https://user-images.githubusercontent.com/26339368/48488290-622ab980-e7ee-11e8-8efd-58d7b834a02f.png)
+
+## Fully Customizable Login Screen :gem:
+
+![image](https://user-images.githubusercontent.com/26339368/48627714-44428d80-e983-11e8-8451-2e873572007a.png)
+
+![image](https://user-images.githubusercontent.com/26339368/48627526-ca120900-e982-11e8-9870-fd469c553124.png)
+
+## Login Screen Background Image :dart:
+
+![image](https://user-images.githubusercontent.com/26339368/47959793-573c8180-dfc3-11e8-84bc-ab654f8c50b5.png)
+
+## Login Screen Custom Note :book:
+
+![image](https://user-images.githubusercontent.com/26339368/64443449-e6ad3800-d09f-11e9-8d77-49e05db75e89.png)
+
+## Includes Preset Themes :mega:
+
+![image](https://user-images.githubusercontent.com/26339368/48488078-f2b4ca00-e7ed-11e8-8d4b-37d5b51f374b.png)
+![image](https://user-images.githubusercontent.com/26339368/48488101-ffd1b900-e7ed-11e8-8438-9ec7b91c98d9.png)
+![image](https://user-images.githubusercontent.com/26339368/49310809-64a73780-f4ad-11e8-81e6-82852275199a.png)
+
+:8ball: KindaDark
+
+<img src="https://i.imgur.com/Mw3DRBM.png" />
+
+
+## Theme Creator :notes:
+
+![image](https://user-images.githubusercontent.com/26339368/49310587-aedbe900-f4ac-11e8-935f-e499c14eb830.png)
+
+![image](https://user-images.githubusercontent.com/26339368/49310623-c024f580-f4ac-11e8-968e-a05762634b80.png)
+
+## Considerations :smirk:
+
+**Will probably not function fully with other CSS plugins that override templates related to layout or headers**
+
+## Theme Plugins that support Customizer
+ * https://github.com/kenlog/nebula :dash:
+ * https://github.com/kenlog/Moon :waning_gibbous_moon:
+ * https://github.com/aljawaid/KanboardCSS :computer:
+ * https://github.com/kenlog/Essential
+
+*Make pull request to add other theme support, must come from theme maintainer.*
+
+## Future Enhancements :lollipop:
+
+- [x] Custom Theme Creator with Color Pickers
+- [x] Preloaded Theme Selector
+- [x] Sizing options
+- [x] Increasing Compatibility
+- [x] Toggle switch to enable/disable cache for images related to Customizer sitewide
+
diff --git a/plugins/Customizer/Schema/Mysql.php b/plugins/Customizer/Schema/Mysql.php
new file mode 100644
index 00000000..978ceb0a
--- /dev/null
+++ b/plugins/Customizer/Schema/Mysql.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Kanboard\Plugin\Customizer\Schema;
+
+use PDO;
+
+const VERSION = 1;
+
+function version_1(PDO $pdo)
+{
+ $pdo->exec("CREATE TABLE customizer_files (
+ id INT NOT NULL AUTO_INCREMENT,
+ custom_id INT NOT NULL,
+ name VARCHAR(255),
+ path VARCHAR(255),
+ is_image TINYINT(1) DEFAULT 0,
+ date INT NOT NULL DEFAULT 0,
+ user_id INT NOT NULL DEFAULT 0,
+ size INT NOT NULL DEFAULT 0,
+ PRIMARY KEY (id)
+ ) ENGINE=InnoDB CHARSET=utf8"
+ );
+}
diff --git a/plugins/Customizer/Schema/Postgres.php b/plugins/Customizer/Schema/Postgres.php
new file mode 100644
index 00000000..54568c69
--- /dev/null
+++ b/plugins/Customizer/Schema/Postgres.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Kanboard\Plugin\Customizer\Schema;
+
+use PDO;
+
+const VERSION = 1;
+
+function version_1(PDO $pdo)
+{
+
+$pdo->exec("
+ CREATE TABLE customizer_files (
+ id SERIAL PRIMARY KEY,
+ custom_id INTEGER NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ path VARCHAR(255) NOT NULL,
+ is_image BOOLEAN DEFAULT '0',
+ size INTEGER DEFAULT 0 NOT NULL,
+ user_id INTEGER DEFAULT 0 NOT NULL,
+ date INTEGER DEFAULT 0 NOT NULL
+ )"
+ );
+
+}
diff --git a/plugins/Customizer/Schema/Sqlite.php b/plugins/Customizer/Schema/Sqlite.php
new file mode 100644
index 00000000..e8da08c1
--- /dev/null
+++ b/plugins/Customizer/Schema/Sqlite.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Kanboard\Plugin\Customizer\Schema;
+
+use PDO;
+
+const VERSION = 1;
+
+function version_1(PDO $pdo)
+{
+
+$pdo->exec("
+ CREATE TABLE customizer_files (
+ id INTEGER PRIMARY KEY,
+ custom_id INTEGER NOT NULL,
+ name TEXT COLLATE NOCASE NOT NULL,
+ path TEXT NOT NULL,
+ is_image INTEGER DEFAULT 0,
+ size INTEGER DEFAULT 0 NOT NULL,
+ user_id INTEGER DEFAULT 0 NOT NULL,
+ date INTEGER DEFAULT 0 NOT NULL
+ )"
+ );
+
+}
diff --git a/plugins/Customizer/Template/board/task_avatar.php b/plugins/Customizer/Template/board/task_avatar.php
new file mode 100644
index 00000000..3dd34a4c
--- /dev/null
+++ b/plugins/Customizer/Template/board/task_avatar.php
@@ -0,0 +1,26 @@
+
+<?php if (! empty($task['owner_id'])): ?>
+<div class="task-board-avatars">
+ <span
+ <?php if ($this->user->hasProjectAccess('TaskModificationController', 'edit', $task['project_id'])): ?>
+ class="task-board-assignee task-board-change-assignee"
+ data-url="<?= $this->url->href('TaskModificationController', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>">
+ <?php else: ?>
+ class="task-board-assignee">
+ <?php endif ?>
+ <?= $this->helper->dynamicAvatar->boardDynamic(
+ $task['owner_id'],
+ $task['assignee_username'],
+ $task['assignee_name'],
+ $task['assignee_email'],
+ $task['assignee_avatar_path'],
+ 'avatar-inline',
+ $this->task->configModel->get('b_av_size', '20')
+ ) ?>
+ </span>
+</div>
+<?php endif ?>
+<style>
+.avatar-bdyn img, .avatar-bdyn div {border-radius: <?= $this->task->configModel->get('b_av_radius', '50') ?>%}
+.avatar-bdyn .avatar-letter {line-height:<?= $this->task->configModel->get('b_av_size', '20') ?>px;width:<?= $this->task->configModel->get('b_av_size', '20') ?>px;font-size:<?= $this->task->configModel->get('b_av_size', '20') / 2 ?>px;}
+</style> \ No newline at end of file
diff --git a/plugins/Customizer/Template/config/sidebar.php b/plugins/Customizer/Template/config/sidebar.php
new file mode 100644
index 00000000..b9d11934
--- /dev/null
+++ b/plugins/Customizer/Template/config/sidebar.php
@@ -0,0 +1,3 @@
+<li <?= $this->app->checkMenuSelection('CustomizerFileController', 'show') ?>>
+ <?= $this->url->link(t('Customizer'), 'CustomizerFileController', 'show', ['plugin' => 'Customizer']) ?>
+</li>
diff --git a/plugins/Customizer/Template/config/themecreator.php b/plugins/Customizer/Template/config/themecreator.php
new file mode 100644
index 00000000..218547da
--- /dev/null
+++ b/plugins/Customizer/Template/config/themecreator.php
@@ -0,0 +1,96 @@
+<form name="themecreator" id="themecreator" class="url-links" method="post" action="<?= $this->url->href('CustomizerConfigController', 'create_theme', array('plugin' => 'customizer', 'redirect' => 'application')) ?>" autocomplete="off">
+<?= $this->form->csrf() ?>
+ <div class="column-100" style="min-height: 600px;">
+ <?= t('Theme Name: ') ?><input type="text" name="theme_name" placeholder="<?= t('Theme Name') ?>" pattern="[a-zA-Z0-9]+" title="<?= t('it should only contain alphanumeric without spaces') ?>" required>
+ <br>
+ <br>
+ <table>
+ <tr>
+ <th colspan="2" class="title-creator">
+ <?= t('Header') ?>
+ </th>
+ <tr>
+ <td>
+ <strong><?= t('Header Background') ?></strong>
+ </td>
+ <td>
+ <input class="color" name="header_background" value="">
+ </td>
+ <td>
+ <strong><?= t('Header Shade') ?></strong>
+ </td>
+ <td>
+ <input class="color" name="header_shade" value="">
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <strong><?= t('Header Title') ?></strong>
+ </td>
+ <td>
+ <input class="color" name="header_title" value="">
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <strong><?= t('Notification Icon') ?></strong>
+ </td>
+ <td>
+ <input class="color" name="notification_icon" value="">
+ </td>
+ </tr>
+ <tr>
+ <th colspan="2" class="title-creator">
+ <?= t('Body') ?>
+ </th>
+ <tr>
+ <tr>
+ <td>
+ <strong><?= t('Background Color') ?></strong>
+ </td>
+ <td>
+ <input class="color" name="background_color" value="">
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <strong><?= t('Main Font and Icons') ?></strong>
+ </td>
+ <td>
+ <input class="color" name="font_main" value="">
+ </td>
+ <td>
+ <strong><?= t('Secondary Fonts and Icons') ?></strong>
+ </td>
+ <td>
+ <input class="color" name="font_secondary" value="">
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <strong><?= t('Links') ?></strong>
+ </td>
+ <td>
+ <input class="color" name="font_link" value="">
+ </td>
+ <td>
+ <strong><?= t('Link Hover & Focus') ?></strong>
+ </td>
+ <td>
+ <input class="color" name="font_link_focus" value="">
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <strong><?= t('Over due') ?></strong>
+ </td>
+ <td>
+ <input class="color" name="font_overdue" value="">
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div class="form-actions" style="margin-bottom: 50px">
+ <button type="submit" class="btn btn-blue"><?= t('Save') ?></button>
+ </div>
+</form>
diff --git a/plugins/Customizer/Template/file/remove.php b/plugins/Customizer/Template/file/remove.php
new file mode 100644
index 00000000..4031f8a8
--- /dev/null
+++ b/plugins/Customizer/Template/file/remove.php
@@ -0,0 +1,15 @@
+<div class="page-header">
+ <h2><?= t('Remove a file') ?></h2>
+</div>
+
+<div class="confirm">
+ <p class="alert alert-info">
+ <?= t('Do you really want to remove this file: "%s"?', $this->text->e($file['name'])) ?>
+ </p>
+
+ <?= $this->modal->confirmButtons(
+ 'CustomizerFileController',
+ 'removeform',
+ array('plugin' => 'customizer', 'custom_id' => $file['custom_id'], 'file_id' => $file['id'])
+ ) ?>
+</div>
diff --git a/plugins/Customizer/Template/file/show.php b/plugins/Customizer/Template/file/show.php
new file mode 100644
index 00000000..c184656c
--- /dev/null
+++ b/plugins/Customizer/Template/file/show.php
@@ -0,0 +1,435 @@
+<?php
+global $customizer;
+?>
+
+<?= $this->hook->render('customizer:config:style') ?>
+
+<div class="sidebar-content">
+ <form name="settings" id="settings" class="url-links" method="post" action="<?= $this->url->href('CustomizerConfigController', 'save', array('plugin' => 'customizer', 'redirect' => 'application')) ?>" autocomplete="off">
+ <?= $this->form->csrf() ?>
+ <fieldset class="login-link-block panel">
+
+ <button type="button" class="login-accordion"><i class="fa fa-picture-o" aria-hidden="true"></i> <?= t('Image Assets & Settings') ?></button>
+ <div class="login-accordian-panel mt-10">
+ <div class="panel header-logo-panel">
+ <div class="panel-heading">
+ <h3 class="panel-title"><?= t('Header Image') ?></h3>
+ </div>
+ <img id="hl1" src="<?= $this->url->href('CustomizerFileController', 'logo_setting', array('plugin' => 'customizer', 'file_id' => $logo['id'])) ?>" alt="<?= $this->text->e($logo['name']) ?>" height="<?= $this->task->configModel->get('headerlogo_size', '30') ?>">
+ <br>
+ <br>
+ <ul class="upload-link">
+ <?php
+ ini_set('display_errors', 1);
+ ini_set('display_startup_errors', 1);
+ error_reporting(E_ALL);
+ ?>
+
+ <?= $this->modal->medium('file', t('Upload Header Logo'), 'CustomizerFileController', 'create', array('plugin' => 'customizer', 'custom_id' => 1))?>
+ </ul class="remove-link">
+ <?php if (null !== $this->task->customizerFileModel->getByType(1)) : ?>
+ <ul class="remove-link">
+ <?php
+ ini_set('display_errors', 1);
+ ini_set('display_startup_errors', 1);
+ error_reporting(E_ALL);
+ ?>
+
+ <?= $this->modal->medium('remove', t('Remove Header Logo'), 'CustomizerFileController', 'confirm', array('plugin' => 'customizer', 'custom_id' => 1, 'file_id' => $logo['id']))?>
+ </ul>
+ <?php endif ?>
+ <br><br>
+ <table>
+ <tr>
+ <th width="25%"><strong><?= t('Header Logo Size') ?></strong></th>
+ <th><input type="range" name="headerlogo_size" min="20" max="250" value="<?= $this->task->configModel->get('headerlogo_size','30') ?>">
+ <header_logo_output> <?= $this->task->configModel->get('headerlogo_size','30') ?></header_logo_output><?= t('&nbsp;pixels high') ?>
+ </th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div class="panel login-logo-panel">
+ <div class="panel-heading">
+ <h3 class="panel-title"><?= t('Login Image') ?></h3>
+ </div>
+
+ <img id="ll1" src="<?= $this->url->href('CustomizerFileController', 'loginlogo_setting', array('plugin' => 'customizer', 'file_id' => $loginlogo['id'])) ?>" alt="<?= $this->text->e($loginlogo['name']) ?>" height="<?= $this->task->configModel->get('loginlogo_size', '50') ?>">
+ <br>
+ <br>
+ <ul class="upload-link">
+ <?php
+ ini_set('display_errors', 1);
+ ini_set('display_startup_errors', 1);
+ error_reporting(E_ALL);
+ ?>
+
+ <?= $this->modal->medium('file', t('Upload Login Logo'), 'CustomizerFileController', 'create', array('plugin' => 'customizer', 'custom_id' => 3))?>
+ </ul>
+ <?php if (null !== $this->task->customizerFileModel->getByType(3)) : ?>
+ <ul class="remove-link">
+ <?php
+ ini_set('display_errors', 1);
+ ini_set('display_startup_errors', 1);
+ error_reporting(E_ALL);
+ ?>
+
+ <?= $this->modal->medium('remove', t('Remove Login Logo'), 'CustomizerFileController', 'confirm', array('plugin' => 'customizer', 'custom_id' => 3, 'file_id' => $loginlogo['id']))?>
+ </ul>
+ <?php endif ?>
+ <br><br>
+ <table>
+ <tr>
+ <th width="25%"><strong><?= t('Login Logo Size') ?></strong></th>
+ <th><input type="range" name="loginlogo_size" min="20" max="500" value="<?= $this->task->configModel->get('loginlogo_size','50') ?>">
+ <login_logo_output><?= $this->task->configModel->get('loginlogo_size','50') ?></login_logo_output><?= t('&nbsp;pixels high') ?>
+ </th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div class="panel favicon-panel">
+ <div class="panel-heading">
+ <h3 class="panel-title"><?= t('Favicon Image') ?></h3>
+ </div>
+
+ <img src="<?= $this->url->href('CustomizerFileController', 'icon_setting', array('plugin' => 'customizer', 'file_id' => $flavicon['id'])) ?>" alt="<?= $this->text->e($flavicon['name']) ?>" height="16">
+ <br>
+ <br>
+ <ul class="upload-link">
+ <?php
+ ini_set('display_errors', 1);
+ ini_set('display_startup_errors', 1);
+ error_reporting(E_ALL);
+ ?>
+ <?= $this->modal->medium('file', t('Upload Favicon'), 'CustomizerFileController', 'create', array('plugin' => 'customizer', 'custom_id' => 2))?>
+ </ul>
+ </ul>
+ <?php if (null !== $this->task->customizerFileModel->getByType(2)) : ?>
+ <ul class="remove-link">
+ <?php
+ ini_set('display_errors', 1);
+ ini_set('display_startup_errors', 1);
+ error_reporting(E_ALL);
+ ?>
+
+ <?= $this->modal->medium('remove', t('Remove Favicon'), 'CustomizerFileController', 'confirm', array('plugin' => 'customizer', 'custom_id' => 2, 'file_id' => $flavicon['id']))?>
+ </ul>
+ <?php endif ?>
+ </div>
+
+ <div class="panel avatar-sizing-panel">
+ <div class="panel-heading">
+ <h3 class="panel-title"><?= t('Header Avatar Icon') ?></h3>
+ </div>
+ <?= $this->helper->dynamicAvatar->currentUserDynamic('avatar-preview') ?>
+ <br>
+ <br>
+ <table>
+ <tr>
+ <th width="25%"><strong><?= t('Header Avatar Icon Size') ?></strong></th>
+ <th><input type="range" name="av_size" id="av_size" min="20" max="50" value="<?= $this->task->configModel->get('av_size','20') ?>">
+ <av_icon_output> <?= $this->task->configModel->get('av_size','20') ?></av_icon_output><?= t('&nbsp;pixels') ?>
+ </th>
+ </tr>
+ <tr>
+ <th width="25%"><strong><?= t('Header Avatar Icon Radius') ?></strong></th>
+ <th><input type="range" name="av_radius" id="av_radius" min="0" max="50" value="<?= $this->task->configModel->get('av_radius','50') ?>">
+ <av_radius_output> <?= $this->task->configModel->get('av_radius','50') ?></av_radius_output><?= t('&nbsp;percent') ?>
+ </th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div class="panel b-avatar-sizing-panel">
+ <div class="panel-heading">
+ <h3 class="panel-title"><?= t('Task Board Avatar Icon') ?></h3>
+ </div>
+ <?= $this->helper->dynamicAvatar->boardCurrentUserDynamic('b-avatar-preview') ?>
+ <br>
+ <br>
+ <table>
+ <tr>
+ <th width="25%"><strong><?= t('Avatar Icon Size') ?></strong></th>
+ <th><input type="range" name="b_av_size" id="b_av_size" min="20" max="50" value="<?= $this->task->configModel->get('b_av_size','20') ?>">
+ <b_av_icon_output> <?= $this->task->configModel->get('b_av_size','20') ?></b_av_icon_output><?= t('&nbsp;pixels') ?>
+ </th>
+ </tr>
+ <tr>
+ <th width="25%"><strong><?= t('Avatar Icon Radius') ?></strong></th>
+ <th><input type="range" name="b_av_radius" id="b_av_radius" min="0" max="50" value="<?= $this->task->configModel->get('b_av_radius','50') ?>">
+ <b_av_radius_output> <?= $this->task->configModel->get('b_av_radius','50') ?></b_av_radius_output><?= t('&nbsp;percent') ?>
+ </th>
+ </tr>
+ </table>
+
+ </div>
+<style>
+.avatar-bdyn img, .avatar-bdyn div {border-radius: <?= $this->task->configModel->get('b_av_radius', '50') ?>%}
+.avatar-bdyn .avatar-letter {line-height:<?= $this->task->configModel->get('b_av_size', '20') ?>px;width:<?= $this->task->configModel->get('b_av_size', '20') ?>px;font-size:<?= $this->task->configModel->get('b_av_size', '20') / 2 ?>px;}
+</style>
+
+
+ <table>
+ <tr>
+ <th width="25%"><strong><?= t('Enable Cache') ?></strong>
+ <p class="form-help enable-cache-desc"><?= e('Once enabled, site assets will begin to be cached for 5 days, increasing speed of site. However, you will need to clear your cache to see any new images uploaded. The settings page, will be unaffected by this setting.') ?></p>
+ </th>
+ <th>
+ <label class="switch">
+ <input id="toggle" name="enable_cache" type="checkbox" value="checked" <?= $this->task->configModel->get('enable_cache','') ?>>
+ <span class="slider round"></span>
+ </label>
+ </th>
+ </tr>
+ </table>
+ <table>
+ <tr>
+ <th width="25%"><strong><?= t('Logo Generator') ?></strong>
+ <p class="form-help enable-cache-desc"><?= e('Experimental Tool, to create simple logos for those in need.') ?></p>
+ </th>
+ <th>
+ <a href="https://creecros.github.io/simple_logo_gen/">
+ <img border="0" alt="logo_gen" src="/plugins/Customizer/Assets/img/logo-gen.png">
+ </a>
+ </th>
+ </table>
+ <div class="form-actions mb-20 ml-15">
+ <button type="submit" name="save" value="save" class="btn btn-blue"><?= t('Save') ?></button>
+ </div>
+ </div>
+
+ <button type="button" class="login-accordion"><i class="fa fa-sign-in" aria-hidden="true"></i> <?= t('Login Page Settings') ?></button>
+ <?php if ($this->task->configModel->get('use_custom_login', '') == 'checked') : ?>
+ <div class="login-accordian-panel mt-10">
+ <?php else :?>
+ <div class="login-accordian-panel mt-10">
+ <?php endif ?>
+ <table>
+ <tr>
+ <th width="25%"><strong><?= t('Use Custom Login Settings') ?></strong></th>
+ <th>
+ <label class="switch">
+ <input id="toggle" name="use_custom_login" type="checkbox" value="checked" <?= $this->task->configModel->get('use_custom_login','') ?>>
+ <span class="slider round"></span>
+ </label>
+ </th>
+ </tr>
+ </table>
+
+ <?php if ($this->task->configModel->get('use_custom_login', '') == 'checked') : ?>
+ <?= $this->form->label(t('Login Link'), 'login_link') ?>
+ <?= $this->form->text('login_link', $values, $errors, array('placeholder="https://example.kanboard.org/"')) ?>
+ <p class="form-help login-link-desc"><?= e('Example: <code>https://example.kanboard.org/</code> (used as logo link on login page)') ?></p>
+ <?= $this->form->label(t('Login Background Image URL'), 'background_url') ?>
+ <?= $this->form->text('background_url', $values, $errors, array('placeholder="https://source.unsplash.com/random"')) ?>
+ <p class="form-help background-img-link-desc"><?= e('Example: <code>https://source.unsplash.com/random</code> (URL for a background image on the login page, centered, autoscale, no-repeat)') ?></p>
+ <?= $this->form->label(t('Login page note'), 'login_note') ?>
+ <?= $this->form->textarea('login_note', $values, $errors, array('placeholder="Welcome to Kanboard!"')) ?>
+ <p class="form-help login-note-desc"><?= e('Hint: Use HTML formatting to customize your note further.') ?></p>
+ <div class="column-100">
+ <table>
+ <tr>
+ <th>
+ <strong><?= t('Login Page Background Color') ?></strong>
+ </th>
+ <th>
+ <input id="loginbackground_color" class="color" name="loginbackground_color" value="<?= $this->task->configModel->get('loginbackground_color','#ffffff') ?>">
+ </th>
+ </tr>
+ <tr>
+ <th>
+ <strong><?= t('Login Panel Shadow Color') ?></strong>
+ </th>
+ <th>
+ <input id="login_shadow_color" class="color" name="login_shadow_color" value="<?= $this->task->configModel->get('login_shadow_color','#333') ?>">
+ </th>
+ </tr>
+ <tr>
+ <th>
+ <strong><?= t('Login Panel Border Color') ?></strong>
+ </th>
+ <th>
+ <input id="login_border_color" class="color" name="login_border_color" value="<?= $this->task->configModel->get('login_border_color','#ffffff') ?>">
+ </th>
+ </tr>
+ <tr>
+ <th>
+ <strong><?= t('Login Panel Border Thickness') ?></strong>
+ </th>
+ <th>
+ <input id="login_border" type="range" name="login_border" min="0" max="10" value="<?= $this->task->configModel->get('login_border','0') ?>">
+ </th>
+ </tr>
+ <tr>
+ <th>
+ <strong><?= t('Login Panel Color') ?></strong>
+ </th>
+ <th>
+ <input id="loginpanel_color" class="color" name="loginpanel_color" value="<?= $this->task->configModel->get('loginpanel_color','#ffffff') ?>">
+ </th>
+ </tr>
+ <tr>
+ <th>
+ <strong><?= t('Login Panel Shadow Intensity') ?></strong>
+ </th>
+ <th>
+ <input id="login_shadow" type="range" name="login_shadow" min="0" max="20" value="<?= $this->task->configModel->get('login_shadow','0') ?>">
+ </th>
+ </tr>
+ <tr>
+ <th>
+ <strong><?= t('Login Button Background Color') ?></strong>
+ </th>
+ <th>
+ <input id="login_btn_color" class="color" name="login_btn_color" value="<?= $this->task->configModel->get('login_btn_color','#3079ed') ?>">
+ </th>
+ </tr>
+ <tr>
+ <th>
+ <strong><?= t('Login Button Shadow Color') ?></strong>
+ </th>
+ <th>
+ <input id="login_btn_shadow_color" class="color" name="login_btn_shadow_color" value="<?= $this->task->configModel->get('login_btn_shadow_color','#333') ?>">
+ </th>
+ </tr>
+ <tr>
+ <th>
+ <strong><?= t('Login Button Border Color') ?></strong>
+ </th>
+ <th>
+ <input id="login_btn_border_color" class="color" name="login_btn_border_color" value="<?= $this->task->configModel->get('login_btn_border_color','transparent') ?>">
+ </th>
+ </tr>
+ <tr>
+ <th>
+ <strong><?= t('Login Button Shade Color') ?></strong>
+ </th>
+ <th>
+ <input id="login_btn_shade_color" class="color" name="login_btn_shade_color" value="<?= $this->task->configModel->get('login_btn_shade_color','transparent') ?>">
+ </th>
+ </tr>
+ <tr>
+ <th>
+ <strong><?= t('Login Button Font Color') ?></strong>
+ </th>
+ <th>
+ <input id="login_btn_font_color" class="color" name="login_btn_font_color" value="<?= $this->task->configModel->get('login_btn_font_color','#ffffff') ?>">
+ </th>
+ </tr>
+ <tr>
+ <th>
+ <strong><?= t('Login Button Shadow Intensity') ?></strong>
+ </th>
+ <th>
+ <input id="login_btn_shadow" type="range" name="login_btn_shadow" min="0" max="20" value="<?= $this->task->configModel->get('login_btn_shadow','0') ?>">
+ </th>
+ </tr>
+ <tr>
+ <th>
+ <strong><?= t('Login Button Border Thickness') ?></strong>
+ </th>
+ <th>
+ <input id="login_btn_border" type="range" name="login_btn_border" min="0" max="10" value="<?= $this->task->configModel->get('login_btn_border','0') ?>">
+ </th>
+ </tr>
+ <tr>
+ <th>
+ <strong><?= t('Login Button Width') ?></strong>
+ </th>
+ <th>
+ <input id="login_btn_width" type="range" name="login_btn_width" min="95" max="300" value="<?= $this->task->configModel->get('login_btn_width','95') ?>">
+ </th>
+ </tr>
+ </table>
+ <p class="alert" style="max-width: 1000px;"><?= t('Changes must be saved in order to take effect.') ?> <button type="submit" name="save" value="save" class="btn btn-blue" style="float: right;margin-top: -6px;"><?= t('Save') ?></button></p>
+ <div class="panel" id="preview" style="background: url('<?= $customizer['backURL'] ?>') no-repeat center center;background-size: cover;height: 700px;max-width: 1000px;background-color: <?= $customizer['backColor'] ?>;">
+ <div>
+ <p style="color: #f5f5f5;"><?= t('Preview') ?></p>
+ </div>
+ <div id="preview-form-login" class="preview-form-login">
+ <?php if ($customizer['loginCheck']): ?>
+ <?= $this->url->link('<img src="' . $this->url->href('CustomizerFileController', 'loginlogo_setting', array('plugin' => 'customizer')) . '" height="' . $customizer['logoSize'] . '">', 'CustomizerFileController', 'link', array('plugin' => 'customizer')) ?>
+ <?php else: ?>
+ <?= $this->url->link('K<span>B</span>', 'DashboardController', 'show', array(), false, '', t('Dashboard')) ?>
+ <?php endif ?>
+
+ <label for="form-username"></label>
+ <input type="text" name="username" placeholder="<?= t('Enter your username') ?>" style="
+ border-radius: 5px;
+ ">
+ <span class="preview-form-required"></span>
+ <label for="form-password"></label>
+ <input type="password" name="password" placeholder="<?= t('Enter your password') ?>" style="
+ border-radius: 5px;
+ ">
+ <span class="preview-form-required"></span>
+ <label style="color:grey"><input type="checkbox" name="remember_me" value="1" checked="checked" disabled>&nbsp; <?= t('Remember Me') ?></label>
+ <div style="margin-bottom: 10px !important;"></div>
+ <div class="preview-form-actions">
+ <button type="button" id="preview-login-btn" class="btn preview-login-btn"><?= t('Sign in') ?></button>
+ </div>
+ <?php if ($this->app->config('password_reset') == 1): ?>
+ <div class="reset-password">
+ <?= $this->url->link(t('Forgot password?'), 'PasswordResetController', 'create') ?>
+ </div>
+ <?php endif ?>
+ </div>
+ <div id="preview-form-note" class="preview-form-note">
+ <div class="login-note">
+ <?= $customizer['login_note'] ?>
+ </div>
+ </div>
+ </div>
+ </div>
+ <?php endif ?>
+ </div>
+ <button type="button" class="login-accordion"><i class="fa fa-refresh" aria-hidden="true"></i> <?= t('Manage Themes') ?></button>
+ <div class="login-accordian-panel mt-10">
+ <div class="panel header-logo-panel">
+ <h3 class="panel-title"><?= t('Global Themes') ?></h3>
+ <?= $this->form->label(t('Theme'), 'themeSelection') ?>
+ <?= $this->helper->themeHelper->reverseSelect('themeSelection', $customizer['themes'], $values, $errors) ?>
+ <p class="form-help theme-select"><?= e('This will be the theme selection for all users who have not chosen their own theme.') ?></p>
+ <div class="form-actions" style="margin-bottom: 50px">
+ <button type="submit" name="save" value="save" class="btn btn-blue"><?= t('Save') ?> </button><button type="submit" name="remove" value="remove" class="btn btn-red"><?= t('Remove') ?></button>
+ </div>
+ </div>
+ </form>
+
+ <form method="post" enctype="multipart/form-data" action="<?= $this->url->href('CustomizerConfigController', 'uploadcss', array('plugin' => 'customizer')) ?>">
+ <div class="panel header-logo-panel">
+ <h3 class="panel-title"><?= t('Upload a theme') ?></h3>
+ <input type="file" name="fileToUpload" id="fileToUpload">
+ <input type="submit" class="btn btn-blue" value="<?= t('Add Theme') ?>" name="submit">
+ </div>
+ </form>
+ <form method="post" enctype="multipart/form-data" action="<?= $this->url->href('CustomizerConfigController', 'resetUserThemes', array('plugin' => 'customizer')) ?>">
+ <div class="panel header-logo-panel">
+ <h3 class="panel-title"><?= t('Users themes option') ?></h3>
+ <input type="submit" class="btn btn-red" value="<?= t('Reset All Users Themes') ?>" name="submit">
+ </div>
+ </form>
+ <form method="post" enctype="multipart/form-data" action="<?= $this->url->href('CustomizerConfigController', 'enableDisableThemes', array('plugin' => 'customizer')) ?>">
+ <div class="panel header-logo-panel">
+ <h3 class="panel-title"><?= t('Toggle Users themes') ?></h3>
+ <?php if ($this->task->configModel->get('toggle_user_themes', 'disable') == 'disable') : ?>
+ <input type="submit" class="btn btn-blue" value="<?= t('Enable Users Themes') ?>" name="submit">
+ <?php else :?>
+ <input type="submit" class="btn btn-red" value="<?= t('Disable Users Themes') ?>" name="submit">
+ <?php endif ?>
+ </div>
+ </form>
+
+
+ </div>
+
+ <button type="button" class="login-accordion"><i class="fa fa-magic" aria-hidden="true"></i> <?= t('Theme Creator') ?></button>
+ <div class="login-accordian-panel mt-10">
+ <?= $this->hook->render('customizer:config:themecreator') ?>
+ </div>
+ </div>
+</fieldset>
diff --git a/plugins/Customizer/Template/file/upload_flavicon.php b/plugins/Customizer/Template/file/upload_flavicon.php
new file mode 100644
index 00000000..b0e8988c
--- /dev/null
+++ b/plugins/Customizer/Template/file/upload_flavicon.php
@@ -0,0 +1,22 @@
+<div class="page-header">
+ <h2><?= t('Favicon') ?></h2>
+ <br>
+ <?= t('Recommend 50x50 pixels, *.png only, max size 20kb.') ?>
+</div>
+
+<?= $this->app->component('file-upload', array(
+ 'maxSize' => 20000,
+ 'url' => $this->url->to('CustomizerFileController', 'save', array('plugin' => 'customizer', 'custom_id' => 2)),
+ 'labelDropzone' => t('Drag and drop your file here'),
+ 'labelOr' => t('or'),
+ 'labelChooseFiles' => t('choose file'),
+ 'labelOversize' => t('The maximum allowed file size is %sB.', $this->text->bytes(20000)),
+ 'labelSuccess' => t('File has been uploaded successfully.'),
+ 'labelCloseSuccess' => t('Close this window'),
+ 'labelUploadError' => t('Unable to upload this file.'),
+)) ?>
+
+<?= $this->modal->submitButtons(array(
+ 'submitLabel' => t('Upload Favicon'),
+ 'disabled' => true,
+)) ?>
diff --git a/plugins/Customizer/Template/file/upload_loginlogo.php b/plugins/Customizer/Template/file/upload_loginlogo.php
new file mode 100644
index 00000000..fa715f36
--- /dev/null
+++ b/plugins/Customizer/Template/file/upload_loginlogo.php
@@ -0,0 +1,22 @@
+<div class="page-header">
+ <h2><?= t('Login Logo') ?></h2>
+ <br>
+ <?= t('Recommend 100 pixels in height, *.png, *.jpg, *.gif, max size 500kb.') ?>
+</div>
+
+<?= $this->app->component('file-upload', array(
+ 'maxSize' => 500000,
+ 'url' => $this->url->to('CustomizerFileController', 'save', array('plugin' => 'customizer', 'custom_id' => 3)),
+ 'labelDropzone' => t('Drag and drop your file here'),
+ 'labelOr' => t('or'),
+ 'labelChooseFiles' => t('choose file'),
+ 'labelOversize' => t('The maximum allowed file size is %sB.', $this->text->bytes(500000)),
+ 'labelSuccess' => t('File has been uploaded successfully.'),
+ 'labelCloseSuccess' => t('Close this window'),
+ 'labelUploadError' => t('Unable to upload this file.'),
+)) ?>
+
+<?= $this->modal->submitButtons(array(
+ 'submitLabel' => t('Upload Login Logo'),
+ 'disabled' => true,
+)) ?>
diff --git a/plugins/Customizer/Template/file/upload_logo.php b/plugins/Customizer/Template/file/upload_logo.php
new file mode 100644
index 00000000..0dae640c
--- /dev/null
+++ b/plugins/Customizer/Template/file/upload_logo.php
@@ -0,0 +1,22 @@
+<div class="page-header">
+ <h2><?= t('Header Logo') ?></h2>
+ <br>
+ <?= t('Recommend 100 pixels in width, *.png, *.jpg, *.gif, max size 500kb.') ?>
+</div>
+
+<?= $this->app->component('file-upload', array(
+ 'maxSize' => 500000,
+ 'url' => $this->url->to('CustomizerFileController', 'save', array('plugin' => 'customizer', 'custom_id' => 1)),
+ 'labelDropzone' => t('Drag and drop your file here'),
+ 'labelOr' => t('or'),
+ 'labelChooseFiles' => t('choose file'),
+ 'labelOversize' => t('The maximum allowed file size is %sB.', $this->text->bytes(500000)),
+ 'labelSuccess' => t('File has been uploaded successfully.'),
+ 'labelCloseSuccess' => t('Close this window'),
+ 'labelUploadError' => t('Unable to upload this file.'),
+)) ?>
+
+<?= $this->modal->submitButtons(array(
+ 'submitLabel' => t('Upload Header Logo'),
+ 'disabled' => true,
+)) ?>
diff --git a/plugins/Customizer/Template/header/title.php b/plugins/Customizer/Template/header/title.php
new file mode 100644
index 00000000..5080c9be
--- /dev/null
+++ b/plugins/Customizer/Template/header/title.php
@@ -0,0 +1,24 @@
+<h1>
+ <?php if (null !== $this->task->customizerFileModel->getByType(1)) : ?>
+ <span class="logo">
+ <a href="<?= $this->url->href('DashboardController', 'show', array(), false, '', t('Dashboard')) ?>">
+ <img src="<?= $this->url->href('CustomizerFileController', 'image', array('plugin' => 'customizer', 'file_id' => $this->task->customizerFileModel->getIdByType(1))) ?>" height="<?= $this->task->configModel->get('headerlogo_size', '30') ?>">
+ </a>
+ </span>
+ <?php else: ?>
+ <span class="logo">
+ <?= $this->url->link('K<span>B</span>', 'DashboardController', 'show', array(), false, '', t('Dashboard')) ?>
+ </span>
+ <?php endif ?>
+
+ <span class="title">
+ <?php if (! empty($project) && ! empty($task)): ?>
+ <?= $this->url->link($this->text->e($project['name']), 'BoardViewController', 'show', array('project_id' => $project['id'])) ?>
+ <?php else: ?>
+ <?= $this->text->e($title) ?>
+ <?php endif ?>
+ </span>
+ <?php if (! empty($description)): ?>
+ <?= $this->app->tooltipHTML($description) ?>
+ <?php endif ?>
+</h1>
diff --git a/plugins/Customizer/Template/header/title_older_kb.php b/plugins/Customizer/Template/header/title_older_kb.php
new file mode 100644
index 00000000..a38ff323
--- /dev/null
+++ b/plugins/Customizer/Template/header/title_older_kb.php
@@ -0,0 +1,26 @@
+<h1>
+ <?php if (null !== $this->task->customizerFileModel->getByType(1)) : ?>
+ <span class="logo">
+ <a href="<?= $this->url->href('DashboardController', 'show', array(), false, '', t('Dashboard')) ?>">
+ <img src="<?= $this->url->href('CustomizerFileController', 'image', array('plugin' => 'customizer', 'file_id' => $this->task->customizerFileModel->getIdByType(1))) ?>" height="<?= $this->task->configModel->get('headerlogo_size', '30') ?>">
+ </a>
+ </span>
+ <?php else: ?>
+ <span class="logo">
+ <?= $this->url->link('K<span>B</span>', 'DashboardController', 'show', array(), false, '', t('Dashboard')) ?>
+ </span>
+ <?php endif ?>
+
+ <span class="title">
+ <?php if (! empty($project) && ! empty($task)): ?>
+ <?= $this->url->link($this->text->e($project['name']), 'BoardViewController', 'show', array('project_id' => $project['id'])) ?>
+ <?php else: ?>
+ <?= $this->text->e($title) ?>
+ <?php endif ?>
+ </span>
+ <?php if (! empty($description)): ?>
+ <small class="tooltip" title="<?= $this->text->markdownAttribute($description) ?>">
+ <i class="fa fa-info-circle"></i>
+ </small>
+ <?php endif ?>
+</h1>
diff --git a/plugins/Customizer/Template/header/user_dropdown.php b/plugins/Customizer/Template/header/user_dropdown.php
new file mode 100644
index 00000000..d302924f
--- /dev/null
+++ b/plugins/Customizer/Template/header/user_dropdown.php
@@ -0,0 +1,45 @@
+<div class="dropdown">
+ <a href="#" class="dropdown-menu dropdown-menu-link-icon" style="display:flex;position:relative;align-items:center;flex-direction:row;"><?= $this->helper->dynamicAvatar->currentUserDynamic('avatar-inline') ?><i class="fa fa-caret-down"></i></a>
+ <ul>
+ <li class="no-hover"><strong><?= $this->text->e($this->user->getFullname()) ?></strong></li>
+ <li>
+ <?= $this->url->icon('tachometer', t('My dashboard'), 'DashboardController', 'show', array('user_id' => $this->user->getId())) ?>
+ </li>
+ <li>
+ <?= $this->url->icon('home', t('My profile'), 'UserViewController', 'show', array('user_id' => $this->user->getId())) ?>
+ </li>
+ <li>
+ <?= $this->url->icon('folder', t('Projects management'), 'ProjectListController', 'show') ?>
+ </li>
+ <?php if ($this->user->hasAccess('UserListController', 'show')): ?>
+ <li>
+ <?= $this->url->icon('user', t('Users management'), 'UserListController', 'show') ?>
+ </li>
+ <li>
+ <?= $this->url->icon('group', t('Groups management'), 'GroupListController', 'index') ?>
+ </li>
+ <li>
+ <?= $this->url->icon('cubes', t('Plugins'), 'PluginController', 'show') ?>
+ </li>
+ <li>
+ <?= $this->url->icon('cog', t('Settings'), 'ConfigController', 'index') ?>
+ </li>
+ <?php endif ?>
+
+ <?= $this->hook->render('template:header:dropdown') ?>
+
+ <li>
+ <i class="fa fa-fw fa-life-ring" aria-hidden="true"></i>
+ <?= $this->url->doc(t('Documentation'), 'index') ?>
+ </li>
+ <?php if (! DISABLE_LOGOUT): ?>
+ <li>
+ <?= $this->url->icon('sign-out', t('Logout'), 'AuthController', 'logout') ?>
+ </li>
+ <?php endif ?>
+ </ul>
+</div>
+<style>
+.avatar-dyn img, .avatar-dyn div {border-radius: <?= $this->task->configModel->get('av_radius', '50') ?>%}
+.avatar-dyn .avatar-letter {line-height:<?= $this->task->configModel->get('av_size', '20') ?>px;width:<?= $this->task->configModel->get('av_size', '20') ?>px;font-size:<?= $this->task->configModel->get('av_size', '20') / 2 ?>px;}
+</style>
diff --git a/plugins/Customizer/Template/layout/index.php b/plugins/Customizer/Template/layout/index.php
new file mode 100644
index 00000000..587b3e04
--- /dev/null
+++ b/plugins/Customizer/Template/layout/index.php
@@ -0,0 +1,47 @@
+<div class="form-login">
+
+ <?= $this->hook->render('template:auth:login-form:before') ?>
+
+ <?php if (isset($errors['login'])): ?>
+ <p class="alert alert-error"><?= $this->text->e($errors['login']) ?></p>
+ <?php endif ?>
+
+ <?php if (! HIDE_LOGIN_FORM): ?>
+ <form method="post" action="<?= $this->url->href('AuthController', 'check') ?>">
+
+ <?= $this->form->csrf() ?>
+
+ <?= $this->form->label(t('Username'), 'username') ?>
+ <?= $this->form->text('username', $values, $errors, array('autofocus', 'required', 'placeholder="Enter your username"')) ?>
+
+ <?= $this->form->label(t('Password'), 'password') ?>
+ <?= $this->form->password('password', $values, $errors, array('required', 'placeholder="Enter your password"')) ?>
+
+ <?php if (isset($captcha) && $captcha): ?>
+ <?= $this->form->label(t('Enter the text below'), 'captcha') ?>
+ <img src="<?= $this->url->href('CaptchaController', 'image') ?>" alt="Captcha">
+ <?= $this->form->text('captcha', array(), $errors, array('required')) ?>
+ <?php endif ?>
+
+ <?php if (REMEMBER_ME_AUTH == true): ?>
+ <?= $this->form->checkbox('remember_me', t('Remember Me'), 1, true) ?>
+ <div class="mb-10"></div>
+ <?php else: ?>
+ <div class="mb-15"></div>
+ <?php endif ?>
+
+ <div class="form-actions">
+ <button type="submit" class="btn login-btn"><?= t('Sign in') ?></button>
+ </div>
+ <?php if ($this->app->config('password_reset') == 1): ?>
+ <div class="reset-password">
+ <?= $this->url->link(t('Forgot password?'), 'PasswordResetController', 'create') ?>
+ </div>
+ <?php endif ?>
+ </form>
+ <?php endif ?>
+
+ <?= $this->hook->render('template:auth:login-form:after') ?>
+</div>
+
+<?= $this->hook->render('template:auth:login-form:newbox') ?>
diff --git a/plugins/Customizer/Template/layout/layout.php b/plugins/Customizer/Template/layout/layout.php
new file mode 100644
index 00000000..e5b4b772
--- /dev/null
+++ b/plugins/Customizer/Template/layout/layout.php
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width">
+ <meta name="mobile-web-app-capable" content="yes">
+ <meta name="robots" content="noindex,nofollow">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="referrer" content="no-referrer">
+
+ <?php if (isset($board_public_refresh_interval)): ?>
+ <meta http-equiv="refresh" content="<?= $board_public_refresh_interval ?>">
+ <?php endif ?>
+
+ <?= $this->asset->colorCss() ?>
+ <?= $this->asset->css('assets/css/vendor.min.css') ?>
+ <?= $this->asset->css('assets/css/app.min.css') ?>
+ <?php if (file_exists('assets/css/print.min.css')) :?>
+ <?= $this->asset->css('assets/css/print.min.css', true, 'print') ?>
+ <?php endif ?>
+ <?= $this->asset->customCss() ?>
+
+ <?php if (! isset($not_editable)): ?>
+ <?= $this->asset->js('assets/js/vendor.min.js') ?>
+ <?= $this->asset->js('assets/js/app.min.js') ?>
+ <?php endif ?>
+
+ <?= $this->hook->asset('css', 'template:layout:css') ?>
+ <?= $this->hook->asset('js', 'template:layout:js') ?>
+ <?php if (null !== $this->task->customizerFileModel->getByType(2)) : ?>
+ <link rel="icon" type="image/png" href="<?= $this->url->href('CustomizerFileController', 'image', array('plugin' => 'customizer', 'file_id' => $this->task->customizerFileModel->getIdByType(2))) ?>">
+ <link rel="apple-touch-icon" href="<?= $this->url->href('CustomizerFileController', 'image', array('plugin' => 'customizer', 'file_id' => $this->task->customizerFileModel->getIdByType(2))) ?>">
+ <link rel="apple-touch-icon" sizes="72x72" href="<?= $this->url->href('CustomizerFileController', 'image', array('plugin' => 'customizer', 'file_id' => $this->task->customizerFileModel->getIdByType(2))) ?>">
+ <link rel="apple-touch-icon" sizes="114x114" href="<?= $this->url->href('CustomizerFileController', 'image', array('plugin' => 'customizer', 'file_id' => $this->task->customizerFileModel->getIdByType(2))) ?>">
+ <link rel="apple-touch-icon" sizes="144x144" href="<?= $this->url->href('CustomizerFileController', 'image', array('plugin' => 'customizer', 'file_id' => $this->task->customizerFileModel->getIdByType(2))) ?>">
+ <?php else: ?>
+ <link rel="icon" type="image/png" href="<?= $this->url->dir() ?>assets/img/favicon.png">
+ <link rel="apple-touch-icon" href="<?= $this->url->dir() ?>assets/img/touch-icon-iphone.png">
+ <link rel="apple-touch-icon" sizes="72x72" href="<?= $this->url->dir() ?>assets/img/touch-icon-ipad.png">
+ <link rel="apple-touch-icon" sizes="114x114" href="<?= $this->url->dir() ?>assets/img/touch-icon-iphone-retina.png">
+ <link rel="apple-touch-icon" sizes="144x144" href="<?= $this->url->dir() ?>assets/img/touch-icon-ipad-retina.png">
+ <?php endif ?>
+
+
+ <title>
+ <?php if (isset($page_title)): ?>
+ <?= $this->text->e($page_title) ?>
+ <?php elseif (isset($title)): ?>
+ <?= $this->text->e($title) ?>
+ <?php else: ?>
+ Kanboard
+ <?php endif ?>
+ </title>
+
+ <?= $this->hook->render('template:layout:head') ?>
+ </head>
+ <body data-status-url="<?= $this->url->href('UserAjaxController', 'status') ?>"
+ data-login-url="<?= $this->url->href('AuthController', 'login') ?>"
+ data-keyboard-shortcut-url="<?= $this->url->href('DocumentationController', 'shortcuts') ?>"
+ data-timezone="<?= $this->app->getTimezone() ?>"
+ data-js-lang="<?= $this->app->jsLang() ?>"
+ data-js-date-format="<?= $this->app->getJsDateFormat() ?>"
+ data-js-time-format="<?= $this->app->getJsTimeFormat() ?>"
+ data-js-modal-close-msg="<?= t('Close window?\\n\\nChanges that you made have not been saved.') ?>"
+ >
+
+ <?php if (isset($no_layout) && $no_layout): ?>
+ <?= $this->app->flashMessage() ?>
+ <?= $content_for_layout ?>
+ <?php else: ?>
+ <?= $this->hook->render('template:layout:top') ?>
+ <?= $this->render('header', array(
+ 'title' => $title,
+ 'description' => isset($description) ? $description : '',
+ 'board_selector' => isset($board_selector) ? $board_selector : array(),
+ 'project' => isset($project) ? $project : array(),
+ )) ?>
+ <section class="page">
+ <?= $this->app->flashMessage() ?>
+ <?= $content_for_layout ?>
+ </section>
+ <?= $this->hook->render('template:layout:bottom') ?>
+ <?php endif ?>
+ </body>
+</html>
diff --git a/plugins/Customizer/Template/layout/login_no_custom.php b/plugins/Customizer/Template/layout/login_no_custom.php
new file mode 100644
index 00000000..7e7cbd54
--- /dev/null
+++ b/plugins/Customizer/Template/layout/login_no_custom.php
@@ -0,0 +1,21 @@
+<?php global $customizer; ?>
+<?php if ($customizer['loginCheck']): ?>
+<?= $this->url->link('<img src="' . $this->url->href('CustomizerFileController', 'loginlogo', array('plugin' => 'customizer')) . '" height="' . $customizer['logoSize'] . '">', 'CustomizerFileController', 'link', array('plugin' => 'customizer')) ?>
+<?php endif ?>
+<?php
+if (function_exists('session_exists')) {
+if (session_exists('redirectAfterLogin') && ! filter_var(session_get('redirectAfterLogin'), FILTER_VALIDATE_URL)) {
+ $redirect = session_get('redirectAfterLogin');
+ if (strpos($redirect, 'Customizer') !== false) {
+ session_remove('redirectAfterLogin');
+ }
+}
+} else {
+if (isset($this->task->sessionStorage->redirectAfterLogin) && ! empty($this->task->sessionStorage->redirectAfterLogin) && ! filter_var($this->task->sessionStorage->redirectAfterLogin, FILTER_VALIDATE_URL)) {
+ $redirect = $this->task->sessionStorage->redirectAfterLogin;
+ if (strpos($redirect, 'Customizer') !== false) {
+ unset($this->task->sessionStorage->redirectAfterLogin);
+ }
+}
+}
+?>
diff --git a/plugins/Customizer/Template/layout/login_with_custom.php b/plugins/Customizer/Template/layout/login_with_custom.php
new file mode 100644
index 00000000..1ca70d77
--- /dev/null
+++ b/plugins/Customizer/Template/layout/login_with_custom.php
@@ -0,0 +1,128 @@
+<?php global $customizer; ?>
+<?php if ($customizer['loginCheck']): ?>
+<?= $this->url->link('<img src="' . $this->url->href('CustomizerFileController', 'loginlogo', array('plugin' => 'customizer')) . '" height="' . $customizer['logoSize'] . '">', 'CustomizerFileController', 'link', array('plugin' => 'customizer')) ?>
+<?php endif ?>
+<style>
+body {
+ background: url("<?= $customizer['backURL'] ?>") no-repeat center center fixed;
+ background-size: cover;
+ background-color: <?= $customizer['backColor'] ?>;
+}
+.mb-10 {
+ margin-bottom: 10px !important;
+}
+.mb-15 {
+ margin-bottom: 15px !important;
+}
+
+.form-login > a > img {
+ display: block;
+ margin: auto;
+ padding-top: 10px
+} /* This aligns the logo to the text. Adds padding to top of logo. */
+
+.form-login {
+ background-color: <?= $customizer['loginpanel_color'] ?>;
+ -webkit-box-shadow: 0px 0px <?= $customizer['login_shadow'] ?>px <?= $customizer['login_shadow'] * .1 ?>px <?= $customizer['login_shadow_color'] ?>;
+ -moz-box-shadow: 0px 0px <?= $customizer['login_shadow'] ?>px <?= $customizer['login_shadow'] * .1 ?>px <?= $customizer['login_shadow_color'] ?>;
+ box-shadow: 0px 0px <?= $customizer['login_shadow'] ?>px <?= $customizer['login_shadow'] * .1 ?>px <?= $customizer['login_shadow_color'] ?>;
+ padding: 10px;
+ border: <?= $customizer['login_border'] ?>px solid <?= $customizer['login_border_color'] ?>;
+ border-radius: 5px;
+ max-width: max-content;
+ text-align: center;
+}
+.login-btn {
+ width: <?= $customizer['login_btn_width'] ?>px;
+ -webkit-box-shadow: 0px 0px <?= $customizer['login_btn_shadow'] ?>px <?= $customizer['login_btn_shadow'] * .1 ?>px <?= $customizer['login_btn_shadow_color'] ?>;
+ -moz-box-shadow: 0px 0px <?= $customizer['login_btn_shadow'] ?>px <?= $customizer['login_btn_shadow'] * .1 ?>px <?= $customizer['login_btn_shadow_color'] ?>;
+ box-shadow: 0px 0px <?= $customizer['login_btn_shadow'] ?>px <?= $customizer['login_btn_shadow'] * .1 ?>px <?= $customizer['login_btn_shadow_color'] ?>;
+ border: <?= $customizer['login_btn_border'] ?>px solid <?= $customizer['login_btn_border_color'] ?>;
+ background: <?= $customizer['login_btn_color'] ?>;
+ color: <?= $customizer['login_btn_font_color'] ?>;
+ background-image: linear-gradient(-180deg, <?= $customizer['login_btn_color'] ?> 0%, <?= $customizer['login_btn_shade_color'] ?> 90%);
+ border-radius: 5px;
+ }
+.login-btn:hover, .login-btn:focus {
+ border-color: <?= $customizer['login_btn_border_color'] ?>;
+ background: <?= $customizer['login_btn_font_color'] ?>;
+ color: <?= $customizer['login_btn_color'] ?>;
+}
+/*------ MOVED FROM PLUGIN CSS FILE TO AVOID AFFECTING OTHER PARTS OF KANBOARD. STYLES SET HERE APPLY ONLY TO THE LOGIN PAGE. ------*/
+.form-actions {
+ text-align: center;
+ padding-top: unset;
+ padding-bottom: 10px;
+} /* This moves the login button to the centre of the box and removes the useless padding above the login button. Adds padding to bottom of login button. */
+
+label:nth-of-type(3n) {
+ color: grey;
+ text-align: center;
+} /* This makes the 'remember me' smaller and centralised*/
+
+.form-actions > .login-btn {
+ font-variant-caps: all-small-caps;
+ text-align: center;
+ transition: cubic-bezier(0.1, 0.75, 0.57, 1) 0.4s;
+ -webkit-transition: cubic-bezier(0.1, 0.75, 0.57, 1) 0.4s;
+} /* This makes the title text of the login button all capitals. Also adds smoothing when hover on the login button */
+
+label:nth-of-type(1) {
+ visibility: hidden;
+} /* This hides (to maintain the gap) the text of the labels */
+
+label:nth-of-type(2n) {
+ visibility: hidden;
+ margin-top: -5px;
+} /* This hides (to maintain the gap) the text of the labels and also reduces the top margin */
+
+input::-webkit-input-placeholder {
+ color: #000;
+ opacity: 1;
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
+} /* This styles the placeholder to emphasise it. Cross-browser compatibility */
+
+input::-moz-placeholder {
+ color: #000;
+ opacity: 1;
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
+} /* This styles the placeholder to emphasise it. Cross-browser compatibility */
+
+input:-ms-input-placeholder {
+ color: #000;
+ opacity: 1;
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
+} /* This styles the placeholder to emphasise it. Cross-browser compatibility */
+
+input::placeholder {
+ color: #000;
+ opacity: 1;
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
+} /* This styles the placeholder to emphasise it. Cross-browser compatibility */
+
+input[type="password"], input[type="text"]:not(.input-addon-field) {
+ margin: auto;
+ display: block;
+ border-radius: 5px;
+} /* This centralises the input fields and makes the borders consistent with the outer form */
+
+.form-required { display: none;} /* This removes the standard required asterisk */
+
+</style>
+<?php
+if (function_exists('session_exists')) {
+if (session_exists('redirectAfterLogin') && ! filter_var(session_get('redirectAfterLogin'), FILTER_VALIDATE_URL)) {
+ $redirect = session_get('redirectAfterLogin');
+ if (strpos($redirect, 'Customizer') !== false) {
+ session_remove('redirectAfterLogin');
+ }
+}
+} else {
+if (isset($this->task->sessionStorage->redirectAfterLogin) && ! empty($this->task->sessionStorage->redirectAfterLogin) && ! filter_var($this->task->sessionStorage->redirectAfterLogin, FILTER_VALIDATE_URL)) {
+ $redirect = $this->task->sessionStorage->redirectAfterLogin;
+ if (strpos($redirect, 'Customizer') !== false) {
+ unset($this->task->sessionStorage->redirectAfterLogin);
+ }
+}
+}
+?>
diff --git a/plugins/Customizer/Template/layout/note.php b/plugins/Customizer/Template/layout/note.php
new file mode 100644
index 00000000..b8454e64
--- /dev/null
+++ b/plugins/Customizer/Template/layout/note.php
@@ -0,0 +1,7 @@
+<?php global $customizer; ?>
+
+<div class="form-login">
+ <div class="login-note">
+ <?= $customizer['login_note'] ?>
+ </div>
+</div>
diff --git a/plugins/Customizer/Template/layout/preview_style.php b/plugins/Customizer/Template/layout/preview_style.php
new file mode 100644
index 00000000..fdd9576f
--- /dev/null
+++ b/plugins/Customizer/Template/layout/preview_style.php
@@ -0,0 +1,77 @@
+<?php global $customizer; ?>
+<style>
+.preview-form-login > a > img {
+ display: block;
+ margin: auto;
+ padding-top: 10px
+} /* This aligns the logo to the text. Adds padding to top of logo. */
+
+
+.preview-form-login, .preview-form-note {
+ background-color: <?= $customizer['loginpanel_color'] ?>;
+ -webkit-box-shadow: 0px 0px <?= $customizer['login_shadow'] ?>px <?= $customizer['login_shadow'] * .1 ?>px;
+ -moz-box-shadow: 0px 0px <?= $customizer['login_shadow'] ?>px <?= $customizer['login_shadow'] * .1 ?>px;
+ box-shadow: 0px 0px <?= $customizer['login_shadow'] ?>px <?= $customizer['login_shadow'] * .1 ?>px;
+ padding: 10px;
+ border: <?= $customizer['login_border'] ?>px solid <?= $customizer['login_border_color'] ?>;
+ border-radius: 5px;
+ text-align: center;
+ max-width: max-content;
+ margin: 5% auto 0;;
+}
+.preview-login-btn {
+ width: <?= $customizer['login_btn_width'] ?>px;
+ -webkit-box-shadow: 0px 0px <?= $customizer['login_btn_shadow'] ?>px <?= $customizer['login_btn_shadow'] * .1 ?>px <?= $customizer['login_btn_shadow_color'] ?>;
+ -moz-box-shadow: 0px 0px <?= $customizer['login_btn_shadow'] ?>px <?= $customizer['login_btn_shadow'] * .1 ?>px <?= $customizer['login_btn_shadow_color'] ?>;
+ box-shadow: 0px 0px <?= $customizer['login_btn_shadow'] ?>px <?= $customizer['login_btn_shadow'] * .1 ?>px <?= $customizer['login_btn_shadow_color'] ?>;
+ border: <?= $customizer['login_btn_border'] ?>px solid <?= $customizer['login_btn_border_color'] ?>;
+ background: <?= $customizer['login_btn_color'] ?>;
+ color: <?= $customizer['login_btn_font_color'] ?>;
+ background-image: linear-gradient(-180deg, transparent 0%, <?= $customizer['login_btn_shade_color'] ?> 90%);
+ border-radius: 5px;
+ }
+
+.preview-login-btn:hover, .preview-login-btn:focus {
+ border-color: <?= $customizer['login_btn_border_color'] ?>;
+ background: <?= $customizer['login_btn_font_color'] ?>;
+ color: <?= $customizer['login_btn_color'] ?>;
+ background-image: unset;
+}
+
+.preview-form-actions > .preview-login-btn {
+ font-variant-caps: all-small-caps;
+ text-align: center;
+ transition: cubic-bezier(0.1, 0.75, 0.57, 1) 0.4s;
+ -webkit-transition: cubic-bezier(0.1, 0.75, 0.57, 1) 0.4s;
+}
+
+
+.preview-form-actions {
+ text-align: center;
+ padding-top: unset;
+ padding-bottom: 10px;
+} /* This moves the login button to the centre of the box and removes the useless padding above the login button. Adds padding to bottom of login button. */
+
+
+
+.preview-form-required { display: none;} /* This removes the standard required asterisk */
+
+.avatar-preview img, .avatar-preview div {
+ border-radius: <?= $this->task->configModel->get('av_radius','50') ?>%;
+}
+.avatar-preview .avatar-letter {
+ line-height: <?= $this->task->configModel->get('av_size','20') ?>px;
+ width: <?= $this->task->configModel->get('av_size','20') ?>px;
+ font-size: <?= $this->task->configModel->get('av_size','20') / 2 ?>px;
+}
+
+.b-avatar-preview img, .b-avatar-preview div {
+ border-radius: <?= $this->task->configModel->get('b_av_radius','50') ?>%;
+}
+.b-avatar-preview .avatar-letter {
+ line-height: <?= $this->task->configModel->get('b_av_size','20') ?>px;
+ width: <?= $this->task->configModel->get('b_av_size','20') ?>px;
+ font-size: <?= $this->task->configModel->get('b_av_size','20') / 2 ?>px;
+}
+
+</style>
diff --git a/plugins/Customizer/Template/user_mod/show.php b/plugins/Customizer/Template/user_mod/show.php
new file mode 100644
index 00000000..a4fbaf96
--- /dev/null
+++ b/plugins/Customizer/Template/user_mod/show.php
@@ -0,0 +1,58 @@
+<?php
+global $customizer;
+$user_theme['themeSelection'] = $this->task->userMetadataModel->get($user['id'], 'themeSelection', $this->task->configModel->get('themeSelection', ''));
+?>
+
+<div class="page-header">
+ <h2><?= t('Edit user') ?></h2>
+</div>
+
+<form method="post" id="ts" action="<?= $this->url->href('CustomizerConfigController', 'usertheme', array('plugin' => 'customizer', 'user_id' => $user['id'])) ?>" autocomplete="off">
+ <?= $this->form->csrf() ?>
+ <?= $this->form->hidden('id', $values) ?>
+ <fieldset>
+ <legend><?= t('Themes') ?></legend>
+ <?= $this->form->label(t('User Theme'), 'themeSelection') ?>
+ <?= $this->helper->themeHelper->reverseSelectOnChange('themeSelection', $customizer['themes'], $user_theme, $errors) ?>
+ </fieldset>
+</form>
+
+<form method="post" action="<?= $this->url->href('UserModificationController', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off">
+ <?= $this->form->csrf() ?>
+ <?= $this->form->hidden('id', $values) ?>
+
+ <fieldset>
+ <legend><?= t('Profile') ?></legend>
+ <?= $this->form->label(t('Username'), 'username') ?>
+ <?= $this->form->text('username', $values, $errors, array('autofocus', 'required', isset($values['is_ldap_user']) && $values['is_ldap_user'] == 1 && !$this->user->isAdmin() ? 'readonly' : '', 'maxlength="191"')) ?>
+
+ <?= $this->form->label(t('Name'), 'name') ?>
+ <?= $this->form->text('name', $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_name') ? '' : 'readonly')) ?>
+
+ <?= $this->form->label(t('Email'), 'email') ?>
+ <?= $this->form->email('email', $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_email') ? '' : 'readonly')) ?>
+ </fieldset>
+
+ <fieldset>
+ <legend><?= t('Preferences') ?></legend>
+ <?= $this->form->label(t('Timezone'), 'timezone') ?>
+ <?= $this->form->select('timezone', $timezones, $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_timezone') ? '' : 'disabled')) ?>
+
+ <?= $this->form->label(t('Language'), 'language') ?>
+ <?= $this->form->select('language', $languages, $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_language') ? '' : 'disabled')) ?>
+
+ <?= $this->form->label(t('Filter'), 'filter') ?>
+ <?= $this->form->text('filter', $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_filter') ? '' : 'readonly')) ?>
+ </fieldset>
+
+ <?php if ($this->user->isAdmin()): ?>
+ <fieldset>
+ <legend><?= t('Security') ?></legend>
+ <?= $this->form->label(t('Application role'), 'role') ?>
+ <?= $this->form->select('role', $roles, $values, $errors) ?>
+ </fieldset>
+ <?php endif ?>
+
+ <?= $this->modal->submitButtons() ?>
+</form>
+ \ No newline at end of file
diff --git a/plugins/Customizer/_config.yml b/plugins/Customizer/_config.yml
new file mode 100644
index 00000000..be854e84
--- /dev/null
+++ b/plugins/Customizer/_config.yml
@@ -0,0 +1,3 @@
+theme: jekyll-theme-cayman
+plugins:
+ - jemoji
diff --git a/plugins/Customizer/composer.json b/plugins/Customizer/composer.json
new file mode 100644
index 00000000..8cc13120
--- /dev/null
+++ b/plugins/Customizer/composer.json
@@ -0,0 +1,5 @@
+{
+ "require": {
+ "luizbills/css-generator": "^3.1"
+ }
+}
diff --git a/plugins/Customizer/composer.lock b/plugins/Customizer/composer.lock
new file mode 100644
index 00000000..15ff3948
--- /dev/null
+++ b/plugins/Customizer/composer.lock
@@ -0,0 +1,164 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "7c8522680994b684990839bf7e954b59",
+ "packages": [
+ {
+ "name": "luizbills/css-generator",
+ "version": "v3.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/luizbills/css-generator.php.git",
+ "reference": "5120ebd4e47f041c5e9bbe3395b05f1486264c26"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/luizbills/css-generator.php/zipball/5120ebd4e47f041c5e9bbe3395b05f1486264c26",
+ "reference": "5120ebd4e47f041c5e9bbe3395b05f1486264c26",
+ "shasum": ""
+ },
+ "require": {
+ "matthiasmullie/minify": "^1.3",
+ "php": ">=5.4.0"
+ },
+ "type": "package",
+ "autoload": {
+ "psr-4": {
+ "luizbills\\CSS_Generator\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Luiz Bills",
+ "email": "luizpbills@gmail.com"
+ }
+ ],
+ "description": "Write CSS programatically using PHP.",
+ "time": "2018-09-21T18:01:30+00:00"
+ },
+ {
+ "name": "matthiasmullie/minify",
+ "version": "1.3.60",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/matthiasmullie/minify.git",
+ "reference": "ab7fea80ce5ce6549baaf272bc8bd926a7e08f90"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/ab7fea80ce5ce6549baaf272bc8bd926a7e08f90",
+ "reference": "ab7fea80ce5ce6549baaf272bc8bd926a7e08f90",
+ "shasum": ""
+ },
+ "require": {
+ "ext-pcre": "*",
+ "matthiasmullie/path-converter": "~1.1",
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.0",
+ "matthiasmullie/scrapbook": "~1.0",
+ "phpunit/phpunit": "~4.8"
+ },
+ "suggest": {
+ "psr/cache-implementation": "Cache implementation to use with Minify::cache"
+ },
+ "bin": [
+ "bin/minifycss",
+ "bin/minifyjs"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "MatthiasMullie\\Minify\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Matthias Mullie",
+ "email": "minify@mullie.eu",
+ "homepage": "http://www.mullie.eu",
+ "role": "Developer"
+ }
+ ],
+ "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.",
+ "homepage": "http://www.minifier.org",
+ "keywords": [
+ "JS",
+ "css",
+ "javascript",
+ "minifier",
+ "minify"
+ ],
+ "time": "2018-04-18T08:50:35+00:00"
+ },
+ {
+ "name": "matthiasmullie/path-converter",
+ "version": "1.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/matthiasmullie/path-converter.git",
+ "reference": "5e4b121c8b9f97c80835c1d878b0812ba1d607c9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/matthiasmullie/path-converter/zipball/5e4b121c8b9f97c80835c1d878b0812ba1d607c9",
+ "reference": "5e4b121c8b9f97c80835c1d878b0812ba1d607c9",
+ "shasum": ""
+ },
+ "require": {
+ "ext-pcre": "*",
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "MatthiasMullie\\PathConverter\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Matthias Mullie",
+ "email": "pathconverter@mullie.eu",
+ "homepage": "http://www.mullie.eu",
+ "role": "Developer"
+ }
+ ],
+ "description": "Relative path converter",
+ "homepage": "http://github.com/matthiasmullie/path-converter",
+ "keywords": [
+ "converter",
+ "path",
+ "paths",
+ "relative"
+ ],
+ "time": "2018-10-25T15:19:41+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": []
+}
diff --git a/plugins/Customizer/vendor/autoload.php b/plugins/Customizer/vendor/autoload.php
new file mode 100644
index 00000000..fe7dc899
--- /dev/null
+++ b/plugins/Customizer/vendor/autoload.php
@@ -0,0 +1,7 @@
+<?php
+
+// autoload.php @generated by Composer
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInit1a4d3188031158859b352a6b53ee769d::getLoader();
diff --git a/plugins/Customizer/vendor/bin/minifycss b/plugins/Customizer/vendor/bin/minifycss
new file mode 100644
index 00000000..04f60a4b
--- /dev/null
+++ b/plugins/Customizer/vendor/bin/minifycss
@@ -0,0 +1 @@
+../matthiasmullie/minify/bin/minifycss \ No newline at end of file
diff --git a/plugins/Customizer/vendor/bin/minifyjs b/plugins/Customizer/vendor/bin/minifyjs
new file mode 100644
index 00000000..61124467
--- /dev/null
+++ b/plugins/Customizer/vendor/bin/minifyjs
@@ -0,0 +1 @@
+../matthiasmullie/minify/bin/minifyjs \ No newline at end of file
diff --git a/plugins/Customizer/vendor/composer/ClassLoader.php b/plugins/Customizer/vendor/composer/ClassLoader.php
new file mode 100644
index 00000000..dc02dfb1
--- /dev/null
+++ b/plugins/Customizer/vendor/composer/ClassLoader.php
@@ -0,0 +1,445 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @see http://www.php-fig.org/psr/psr-0/
+ * @see http://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ // PSR-4
+ private $prefixLengthsPsr4 = array();
+ private $prefixDirsPsr4 = array();
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ private $prefixesPsr0 = array();
+ private $fallbackDirsPsr0 = array();
+
+ private $useIncludePath = false;
+ private $classMap = array();
+ private $classMapAuthoritative = false;
+ private $missingClasses = array();
+ private $apcuPrefix;
+
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', $this->prefixesPsr0);
+ }
+
+ return array();
+ }
+
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ (array) $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ (array) $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 base directories
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return bool|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ includeFile($file);
+
+ return true;
+ }
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath.'\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+ include $file;
+}
diff --git a/plugins/Customizer/vendor/composer/LICENSE b/plugins/Customizer/vendor/composer/LICENSE
new file mode 100644
index 00000000..f27399a0
--- /dev/null
+++ b/plugins/Customizer/vendor/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/plugins/Customizer/vendor/composer/autoload_classmap.php b/plugins/Customizer/vendor/composer/autoload_classmap.php
new file mode 100644
index 00000000..7a91153b
--- /dev/null
+++ b/plugins/Customizer/vendor/composer/autoload_classmap.php
@@ -0,0 +1,9 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+);
diff --git a/plugins/Customizer/vendor/composer/autoload_namespaces.php b/plugins/Customizer/vendor/composer/autoload_namespaces.php
new file mode 100644
index 00000000..b7fc0125
--- /dev/null
+++ b/plugins/Customizer/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,9 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+);
diff --git a/plugins/Customizer/vendor/composer/autoload_psr4.php b/plugins/Customizer/vendor/composer/autoload_psr4.php
new file mode 100644
index 00000000..05c26176
--- /dev/null
+++ b/plugins/Customizer/vendor/composer/autoload_psr4.php
@@ -0,0 +1,12 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'luizbills\\CSS_Generator\\' => array($vendorDir . '/luizbills/css-generator/src'),
+ 'MatthiasMullie\\PathConverter\\' => array($vendorDir . '/matthiasmullie/path-converter/src'),
+ 'MatthiasMullie\\Minify\\' => array($vendorDir . '/matthiasmullie/minify/src'),
+);
diff --git a/plugins/Customizer/vendor/composer/autoload_real.php b/plugins/Customizer/vendor/composer/autoload_real.php
new file mode 100644
index 00000000..49558bf6
--- /dev/null
+++ b/plugins/Customizer/vendor/composer/autoload_real.php
@@ -0,0 +1,52 @@
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInit1a4d3188031158859b352a6b53ee769d
+{
+ private static $loader;
+
+ public static function loadClassLoader($class)
+ {
+ if ('Composer\Autoload\ClassLoader' === $class) {
+ require __DIR__ . '/ClassLoader.php';
+ }
+ }
+
+ public static function getLoader()
+ {
+ if (null !== self::$loader) {
+ return self::$loader;
+ }
+
+ spl_autoload_register(array('ComposerAutoloaderInit1a4d3188031158859b352a6b53ee769d', 'loadClassLoader'), true, true);
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader();
+ spl_autoload_unregister(array('ComposerAutoloaderInit1a4d3188031158859b352a6b53ee769d', 'loadClassLoader'));
+
+ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+ if ($useStaticLoader) {
+ require_once __DIR__ . '/autoload_static.php';
+
+ call_user_func(\Composer\Autoload\ComposerStaticInit1a4d3188031158859b352a6b53ee769d::getInitializer($loader));
+ } else {
+ $map = require __DIR__ . '/autoload_namespaces.php';
+ foreach ($map as $namespace => $path) {
+ $loader->set($namespace, $path);
+ }
+
+ $map = require __DIR__ . '/autoload_psr4.php';
+ foreach ($map as $namespace => $path) {
+ $loader->setPsr4($namespace, $path);
+ }
+
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+ }
+
+ $loader->register(true);
+
+ return $loader;
+ }
+}
diff --git a/plugins/Customizer/vendor/composer/autoload_static.php b/plugins/Customizer/vendor/composer/autoload_static.php
new file mode 100644
index 00000000..11ddf899
--- /dev/null
+++ b/plugins/Customizer/vendor/composer/autoload_static.php
@@ -0,0 +1,44 @@
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInit1a4d3188031158859b352a6b53ee769d
+{
+ public static $prefixLengthsPsr4 = array (
+ 'l' =>
+ array (
+ 'luizbills\\CSS_Generator\\' => 24,
+ ),
+ 'M' =>
+ array (
+ 'MatthiasMullie\\PathConverter\\' => 29,
+ 'MatthiasMullie\\Minify\\' => 22,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'luizbills\\CSS_Generator\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/luizbills/css-generator/src',
+ ),
+ 'MatthiasMullie\\PathConverter\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/matthiasmullie/path-converter/src',
+ ),
+ 'MatthiasMullie\\Minify\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/matthiasmullie/minify/src',
+ ),
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInit1a4d3188031158859b352a6b53ee769d::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInit1a4d3188031158859b352a6b53ee769d::$prefixDirsPsr4;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/plugins/Customizer/vendor/composer/installed.json b/plugins/Customizer/vendor/composer/installed.json
new file mode 100644
index 00000000..9823b6e4
--- /dev/null
+++ b/plugins/Customizer/vendor/composer/installed.json
@@ -0,0 +1,154 @@
+[
+ {
+ "name": "luizbills/css-generator",
+ "version": "v3.1.1",
+ "version_normalized": "3.1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/luizbills/css-generator.php.git",
+ "reference": "5120ebd4e47f041c5e9bbe3395b05f1486264c26"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/luizbills/css-generator.php/zipball/5120ebd4e47f041c5e9bbe3395b05f1486264c26",
+ "reference": "5120ebd4e47f041c5e9bbe3395b05f1486264c26",
+ "shasum": ""
+ },
+ "require": {
+ "matthiasmullie/minify": "^1.3",
+ "php": ">=5.4.0"
+ },
+ "time": "2018-09-21T18:01:30+00:00",
+ "type": "package",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "luizbills\\CSS_Generator\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Luiz Bills",
+ "email": "luizpbills@gmail.com"
+ }
+ ],
+ "description": "Write CSS programatically using PHP."
+ },
+ {
+ "name": "matthiasmullie/minify",
+ "version": "1.3.60",
+ "version_normalized": "1.3.60.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/matthiasmullie/minify.git",
+ "reference": "ab7fea80ce5ce6549baaf272bc8bd926a7e08f90"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/ab7fea80ce5ce6549baaf272bc8bd926a7e08f90",
+ "reference": "ab7fea80ce5ce6549baaf272bc8bd926a7e08f90",
+ "shasum": ""
+ },
+ "require": {
+ "ext-pcre": "*",
+ "matthiasmullie/path-converter": "~1.1",
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.0",
+ "matthiasmullie/scrapbook": "~1.0",
+ "phpunit/phpunit": "~4.8"
+ },
+ "suggest": {
+ "psr/cache-implementation": "Cache implementation to use with Minify::cache"
+ },
+ "time": "2018-04-18T08:50:35+00:00",
+ "bin": [
+ "bin/minifycss",
+ "bin/minifyjs"
+ ],
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "MatthiasMullie\\Minify\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Matthias Mullie",
+ "email": "minify@mullie.eu",
+ "homepage": "http://www.mullie.eu",
+ "role": "Developer"
+ }
+ ],
+ "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.",
+ "homepage": "http://www.minifier.org",
+ "keywords": [
+ "JS",
+ "css",
+ "javascript",
+ "minifier",
+ "minify"
+ ]
+ },
+ {
+ "name": "matthiasmullie/path-converter",
+ "version": "1.1.2",
+ "version_normalized": "1.1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/matthiasmullie/path-converter.git",
+ "reference": "5e4b121c8b9f97c80835c1d878b0812ba1d607c9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/matthiasmullie/path-converter/zipball/5e4b121c8b9f97c80835c1d878b0812ba1d607c9",
+ "reference": "5e4b121c8b9f97c80835c1d878b0812ba1d607c9",
+ "shasum": ""
+ },
+ "require": {
+ "ext-pcre": "*",
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "time": "2018-10-25T15:19:41+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "MatthiasMullie\\PathConverter\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Matthias Mullie",
+ "email": "pathconverter@mullie.eu",
+ "homepage": "http://www.mullie.eu",
+ "role": "Developer"
+ }
+ ],
+ "description": "Relative path converter",
+ "homepage": "http://github.com/matthiasmullie/path-converter",
+ "keywords": [
+ "converter",
+ "path",
+ "paths",
+ "relative"
+ ]
+ }
+]
diff --git a/plugins/Customizer/vendor/luizbills/css-generator/.gitignore b/plugins/Customizer/vendor/luizbills/css-generator/.gitignore
new file mode 100644
index 00000000..55940e57
--- /dev/null
+++ b/plugins/Customizer/vendor/luizbills/css-generator/.gitignore
@@ -0,0 +1,2 @@
+/vendor/
+composer.lock \ No newline at end of file
diff --git a/plugins/Customizer/vendor/luizbills/css-generator/LICENSE b/plugins/Customizer/vendor/luizbills/css-generator/LICENSE
new file mode 100644
index 00000000..7f9fca91
--- /dev/null
+++ b/plugins/Customizer/vendor/luizbills/css-generator/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Luiz Bills
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/plugins/Customizer/vendor/luizbills/css-generator/README.md b/plugins/Customizer/vendor/luizbills/css-generator/README.md
new file mode 100644
index 00000000..28dee232
--- /dev/null
+++ b/plugins/Customizer/vendor/luizbills/css-generator/README.md
@@ -0,0 +1,102 @@
+# CSS Generator
+
+Write CSS programatically using PHP.
+
+## Install
+
+```php
+composer require luizbills/css-generator
+```
+
+## Usage
+
+```php
+use luizbills\CSS_Generator\Generator as CSS_Generator;
+
+$options = [
+ // default values
+ // 'indentation' => ' ', // 4 spaces
+];
+$css = new CSS_Generator( $options );
+
+// single selector
+$css->add_rule( '.color-white', [ 'color' => '#fff' ] );
+
+$css->open_block( 'media', 'screen and (min-width: 30em)' );
+
+// multiple selectors
+$css->add_rule( [ 'html', 'body' ], [
+ 'background-color' => 'black',
+ 'color' => 'white'
+] );
+
+$css->close_block(); // close a block
+
+$css->open_block( 'supports', '(display: grid)' );
+
+$css->add_rule( '.grid', [
+ 'display' => 'grid',
+] );
+
+// nested block
+$css->open_block( 'media', 'screen and (max-width: 30em)' );
+
+$css->add_rule( '.grid-sm', [
+ 'display' => 'grid',
+] );
+
+$css->close_blocks(); // close all blocks
+
+$minify = false;
+echo $css->get_output( $minify );
+```
+
+output:
+```css
+.color-white {
+ color: #fff;
+}
+@media screen and (min-width: 30em) {
+ html,
+ body {
+ background-color: black;
+ color: white;
+ }
+}
+@supports (display: grid) {
+ .grid {
+ display: grid;
+ }
+ @media screen and (max-width: 30em) {
+ .grid-sm {
+ display: grid;
+ }
+ }
+}
+```
+
+Changing `$minify` to `true` will outputs:
+```css
+.color-white{color:#fff}@media screen and (min-width:30em){body,html{background-color:#000;color:#fff}}@supports (display:grid){.grid{display:grid}@media screen and (max-width:30em){.grid-sm{display:grid}}}
+```
+
+There is also a method `add_raw` that adds any string to your css. Useful to comments or include a framework.
+```php
+$css = new CSS_Generator();
+
+$css->add_rule( '.color-white', [ 'color' => '#fff' ] );
+$css->add_raw('/* my comment */ a { text-decoration: none }');
+
+echo $css->get_output();
+```
+
+output:
+```css
+.color-white{
+ color:#fff;
+}
+/* my comment */ a { text-decoration: none }
+```
+
+## License
+MIT License &copy; 2018 Luiz Bills
diff --git a/plugins/Customizer/vendor/luizbills/css-generator/composer.json b/plugins/Customizer/vendor/luizbills/css-generator/composer.json
new file mode 100644
index 00000000..c6e9417c
--- /dev/null
+++ b/plugins/Customizer/vendor/luizbills/css-generator/composer.json
@@ -0,0 +1,21 @@
+{
+ "name": "luizbills/css-generator",
+ "description": "Write CSS programatically using PHP.",
+ "type": "package",
+ "require": {
+ "php": ">=5.4.0",
+ "matthiasmullie/minify": "^1.3"
+ },
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Luiz Bills",
+ "email": "luizpbills@gmail.com"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "luizbills\\CSS_Generator\\": "src"
+ }
+ }
+}
diff --git a/plugins/Customizer/vendor/luizbills/css-generator/demo/demo.php b/plugins/Customizer/vendor/luizbills/css-generator/demo/demo.php
new file mode 100644
index 00000000..69e6a4a1
--- /dev/null
+++ b/plugins/Customizer/vendor/luizbills/css-generator/demo/demo.php
@@ -0,0 +1,41 @@
+<?php
+
+require_once __DIR__ . '/../vendor/autoload.php';
+
+use luizbills\CSS_Generator\Generator as CSS_Generator;
+
+$options = [
+ 'indentation' => ' ', // 2 spaces
+];
+$css = new CSS_Generator( $options );
+
+// single selector
+$css->add_rule( '.color-white', [ 'color' => '#fff' ] );
+
+$css->open_block( 'media', 'screen and (min-width: 30em)' );
+
+// multiple selectors
+$css->add_rule( [ 'html', 'body' ], [
+ 'background-color' => 'black',
+ 'color' => 'white'
+] );
+
+$css->close_block(); // close a block
+
+$css->open_block( 'supports', '(display: grid)' );
+
+$css->add_rule( '.grid', [
+ 'display' => 'grid',
+] );
+
+// nested block
+$css->open_block( 'media', 'screen and (max-width: 30em)' );
+
+$css->add_rule( '.grid-sm', [
+ 'display' => 'grid',
+] );
+
+$css->close_blocks(); // close all blocks
+
+$minify = false;
+echo $css->get_output( $minify ); \ No newline at end of file
diff --git a/plugins/Customizer/vendor/luizbills/css-generator/src/Generator.php b/plugins/Customizer/vendor/luizbills/css-generator/src/Generator.php
new file mode 100644
index 00000000..6ef80f00
--- /dev/null
+++ b/plugins/Customizer/vendor/luizbills/css-generator/src/Generator.php
@@ -0,0 +1,102 @@
+<?php
+/**
+ * CSS Generator
+ * Write css programatically using PHP.
+ *
+ * @author Luiz Bills <luizpbills@gmail.comm>
+ * @copyright 2018 Luiz Bills
+ * @license MIT
+*/
+namespace luizbills\CSS_Generator;
+
+use MatthiasMullie\Minify;
+
+class Generator {
+ const VERSION = '3.1.0';
+
+ protected $raw = '';
+ protected $block_level = 0;
+ protected $linebreak = "\n";
+ protected $minified = null; // for cache
+ protected $options = null;
+ protected $default_options = [
+ 'indentation' => ' ', // 4 spaces
+ ];
+
+ public function __construct ( $options = [] ) {
+ $this->options = array_merge( $this->default_options, $options );
+ }
+
+ public function get_output ( $compress = false ) {
+ $this->close_blocks();
+ if ( $compress ) {
+ return $this->minify();
+ }
+ return $this->raw;
+ }
+
+ protected function minify () {
+ if ( ! is_null( $this->minified ) ) {
+ return $this->minified;
+ }
+
+ $minifier = new Minify\CSS( $this->raw );
+ $this->minified = $minifier->minify();
+ return $this->minified;
+ }
+
+ public function add_raw ( $string ) {
+ $this->raw .= $string;
+ $this->clear_cache();
+ }
+
+ public function add_rule ( $selectors, $declarations_array ) {
+ $declarations = [];
+ $selector_indentation = str_repeat( $this->options['indentation'], $this->block_level );
+ $declaration_indentation = str_repeat( $this->options['indentation'], $this->block_level + 1 );
+
+ if ( ! is_array( $selectors ) ) {
+ $selectors = [ $selectors ];
+ }
+
+ foreach ( $selectors as $key => $value ) {
+ $selectors[ $key ] = $selector_indentation . trim( $value );
+ }
+
+ foreach ( $declarations_array as $key => $value ) {
+ $declarations[] = $declaration_indentation . trim( $key ) . ': ' . trim( $value ) . ';' . $this->linebreak;
+ }
+
+ $this->raw .= implode( ',' . $this->linebreak, $selectors ) . ' {';
+ $this->raw .= $this->linebreak . implode( '', $declarations );
+ $this->raw .= $selector_indentation . '}' . $this->linebreak;
+
+ $this->clear_cache();
+ }
+
+ public function open_block ( $type, $props = '' ) {
+ $block_indentation = str_repeat( $this->options['indentation'], $this->block_level );
+ $this->raw .= $block_indentation . '@' . $type . ' ' . trim( $props ) . ' {' . $this->linebreak;
+ $this->block_level++;
+ $this->clear_cache();
+ }
+
+ public function close_block () {
+ if ( $this->block_level > 0 ) {
+ $this->block_level--;
+ $block_indentation = str_repeat( $this->options['indentation'], $this->block_level );
+ $this->raw .= $block_indentation . '}' . $this->linebreak;
+ $this->clear_cache();
+ }
+ }
+
+ public function close_blocks () {
+ while ( $this->block_level > 0 ) {
+ $this->close_block();
+ }
+ }
+
+ protected function clear_cache() {
+ $this->minified = null;
+ }
+}
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/CONTRIBUTING.md b/plugins/Customizer/vendor/matthiasmullie/minify/CONTRIBUTING.md
new file mode 100644
index 00000000..226cf976
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/CONTRIBUTING.md
@@ -0,0 +1,59 @@
+# How to contribute
+
+
+## Issues
+
+When [filing bugs](https://github.com/matthiasmullie/minify/issues/new),
+try to be as thorough as possible:
+* What version did you use?
+* What did you try to do? ***Please post the relevant parts of your code.***
+* What went wrong? ***Please include error messages, if any.***
+* What was the expected result?
+
+
+## Pull requests
+
+Bug fixes and general improvements to the existing codebase are always welcome.
+New features are also welcome, but will be judged on an individual basis. If
+you'd rather not risk wasting your time implementing a new feature only to see
+it turned down, please start the discussion by
+[opening an issue](https://github.com/matthiasmullie/minify/issues/new).
+
+Don't forget to add your changes to the [changelog](CHANGELOG.md).
+
+
+### Testing
+
+Please include tests for every change or addition to the code.
+To run the complete test suite:
+
+```sh
+vendor/bin/phpunit
+```
+
+When submitting a new pull request, please make sure that that the test suite
+passes (Travis CI will run it & report back on your pull request.)
+
+To run the tests on Windows, run `tests/convert_symlinks_to_windows_style.sh`
+from the command line in order to convert Linux-style test symlinks to
+Windows-style.
+
+
+### Coding standards
+
+All code must follow [PSR-2](http://www.php-fig.org/psr/psr-2/). Just make sure
+to run php-cs-fixer before submitting the code, it'll take care of the
+formatting for you:
+
+```sh
+vendor/bin/php-cs-fixer fix src
+vendor/bin/php-cs-fixer fix tests
+```
+
+Document the code thoroughly!
+
+
+## License
+
+Note that minify is MIT-licensed, which basically allows anyone to do
+anything they like with it, without restriction.
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/Dockerfile b/plugins/Customizer/vendor/matthiasmullie/minify/Dockerfile
new file mode 100644
index 00000000..d17f9d74
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/Dockerfile
@@ -0,0 +1,13 @@
+ARG version=cli
+FROM php:$version
+
+COPY . /var/www
+WORKDIR /var/www
+
+RUN apt-get update
+RUN apt-get install -y zip unzip zlib1g-dev
+RUN docker-php-ext-install zip
+RUN docker-php-ext-install pcntl
+RUN curl -sS https://getcomposer.org/installer | php
+RUN mv composer.phar /usr/local/bin/composer
+RUN composer install
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/LICENSE b/plugins/Customizer/vendor/matthiasmullie/minify/LICENSE
new file mode 100644
index 00000000..0c0d08a7
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/LICENSE
@@ -0,0 +1,18 @@
+Copyright (c) 2012 Matthias Mullie
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/bin/minifycss b/plugins/Customizer/vendor/matthiasmullie/minify/bin/minifycss
new file mode 100644
index 00000000..6a681a85
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/bin/minifycss
@@ -0,0 +1,45 @@
+#!/usr/bin/env php
+<?php
+use MatthiasMullie\Minify;
+
+// command line utility to minify CSS
+if (file_exists(__DIR__ . '/../../../autoload.php')) {
+ // if composer install
+ require_once __DIR__ . '/../../../autoload.php';
+} else {
+ require_once __DIR__ . '/../src/Minify.php';
+ require_once __DIR__ . '/../src/CSS.php';
+ require_once __DIR__ . '/../src/Exception.php';
+}
+
+error_reporting(E_ALL);
+// check PHP setup for cli arguments
+if (!isset($_SERVER['argv']) && !isset($argv)) {
+ fwrite(STDERR, 'Please enable the "register_argc_argv" directive in your php.ini' . PHP_EOL);
+ exit(1);
+} elseif (!isset($argv)) {
+ $argv = $_SERVER['argv'];
+}
+// check if path to file given
+if (!isset($argv[1])) {
+ fwrite(STDERR, 'Argument expected: path to file' . PHP_EOL);
+ exit(1);
+}
+// check if script run in cli environment
+if ('cli' !== php_sapi_name()) {
+ fwrite(STDERR, $argv[1] . ' must be run in the command line' . PHP_EOL);
+ exit(1);
+}
+// check if source file exists
+if (!file_exists($argv[1])) {
+ fwrite(STDERR, 'Source file "' . $argv[1] . '" not found' . PHP_EOL);
+ exit(1);
+}
+
+try {
+ $minifier = new Minify\CSS($argv[1]);
+ echo $minifier->minify();
+} catch (Exception $e) {
+ fwrite(STDERR, $e->getMessage(), PHP_EOL);
+ exit(1);
+}
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/bin/minifyjs b/plugins/Customizer/vendor/matthiasmullie/minify/bin/minifyjs
new file mode 100644
index 00000000..4cbe63ff
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/bin/minifyjs
@@ -0,0 +1,45 @@
+#!/usr/bin/env php
+<?php
+use MatthiasMullie\Minify;
+
+// command line utility to minify JS
+if (file_exists(__DIR__ . '/../../../autoload.php')) {
+ // if composer install
+ require_once __DIR__ . '/../../../autoload.php';
+} else {
+ require_once __DIR__ . '/../src/Minify.php';
+ require_once __DIR__ . '/../src/JS.php';
+ require_once __DIR__ . '/../src/Exception.php';
+}
+
+error_reporting(E_ALL);
+// check PHP setup for cli arguments
+if (!isset($_SERVER['argv']) && !isset($argv)) {
+ fwrite(STDERR, 'Please enable the "register_argc_argv" directive in your php.ini' . PHP_EOL);
+ exit(1);
+} elseif (!isset($argv)) {
+ $argv = $_SERVER['argv'];
+}
+// check if path to file given
+if (!isset($argv[1])) {
+ fwrite(STDERR, 'Argument expected: path to file' . PHP_EOL);
+ exit(1);
+}
+// check if script run in cli environment
+if ('cli' !== php_sapi_name()) {
+ fwrite(STDERR, $argv[1] . ' must be run in the command line' . PHP_EOL);
+ exit(1);
+}
+// check if source file exists
+if (!file_exists($argv[1])) {
+ fwrite(STDERR, 'Source file "' . $argv[1] . '" not found' . PHP_EOL);
+ exit(1);
+}
+
+try {
+ $minifier = new Minify\JS($argv[1]);
+ echo $minifier->minify();
+} catch (Exception $e) {
+ fwrite(STDERR, $e->getMessage(), PHP_EOL);
+ exit(1);
+}
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/composer.json b/plugins/Customizer/vendor/matthiasmullie/minify/composer.json
new file mode 100644
index 00000000..6d81b4f9
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/composer.json
@@ -0,0 +1,38 @@
+{
+ "name": "matthiasmullie/minify",
+ "type": "library",
+ "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.",
+ "keywords": ["minify", "minifier", "css", "js", "javascript"],
+ "homepage": "http://www.minifier.org",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Matthias Mullie",
+ "homepage": "http://www.mullie.eu",
+ "email": "minify@mullie.eu",
+ "role": "Developer"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0",
+ "ext-pcre": "*",
+ "matthiasmullie/path-converter": "~1.1"
+ },
+ "require-dev": {
+ "matthiasmullie/scrapbook": "~1.0",
+ "phpunit/phpunit": "~4.8",
+ "friendsofphp/php-cs-fixer": "~2.0"
+ },
+ "suggest": {
+ "psr/cache-implementation": "Cache implementation to use with Minify::cache"
+ },
+ "autoload": {
+ "psr-4": {
+ "MatthiasMullie\\Minify\\": "src/"
+ }
+ },
+ "bin": [
+ "bin/minifycss",
+ "bin/minifyjs"
+ ]
+}
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_after.txt b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_after.txt
new file mode 100644
index 00000000..5c8cba7f
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_after.txt
@@ -0,0 +1,7 @@
+in
+public
+extends
+private
+protected
+implements
+instanceof \ No newline at end of file
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_before.txt b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_before.txt
new file mode 100644
index 00000000..5abf3579
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_before.txt
@@ -0,0 +1,26 @@
+do
+in
+let
+new
+var
+case
+else
+enum
+void
+with
+class
+const
+yield
+delete
+export
+import
+public
+static
+typeof
+extends
+package
+private
+function
+protected
+implements
+instanceof \ No newline at end of file
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_reserved.txt b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_reserved.txt
new file mode 100644
index 00000000..2a3ad3c0
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_reserved.txt
@@ -0,0 +1,63 @@
+do
+if
+in
+for
+let
+new
+try
+var
+case
+else
+enum
+eval
+null
+this
+true
+void
+with
+break
+catch
+class
+const
+false
+super
+throw
+while
+yield
+delete
+export
+import
+public
+return
+static
+switch
+typeof
+default
+extends
+finally
+package
+private
+continue
+debugger
+function
+arguments
+interface
+protected
+implements
+instanceof
+abstract
+boolean
+byte
+char
+double
+final
+float
+goto
+int
+long
+native
+short
+synchronized
+throws
+transient
+volatile \ No newline at end of file
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators.txt b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators.txt
new file mode 100644
index 00000000..e66229ae
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators.txt
@@ -0,0 +1,46 @@
++
+-
+*
+/
+%
+=
++=
+-=
+*=
+/=
+%=
+<<=
+>>=
+>>>=
+&=
+^=
+|=
+&
+|
+^
+~
+<<
+>>
+>>>
+==
+===
+!=
+!==
+>
+<
+>=
+<=
+&&
+||
+!
+.
+[
+]
+?
+:
+,
+;
+(
+)
+{
+} \ No newline at end of file
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators_after.txt b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators_after.txt
new file mode 100644
index 00000000..71a9b709
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators_after.txt
@@ -0,0 +1,43 @@
++
+-
+*
+/
+%
+=
++=
+-=
+*=
+/=
+%=
+<<=
+>>=
+>>>=
+&=
+^=
+|=
+&
+|
+^
+<<
+>>
+>>>
+==
+===
+!=
+!==
+>
+<
+>=
+<=
+&&
+||
+.
+[
+]
+?
+:
+,
+;
+(
+)
+} \ No newline at end of file
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators_before.txt b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators_before.txt
new file mode 100644
index 00000000..ff50d870
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators_before.txt
@@ -0,0 +1,43 @@
++
+-
+*
+/
+%
+=
++=
+-=
+*=
+/=
+%=
+<<=
+>>=
+>>>=
+&=
+^=
+|=
+&
+|
+^
+~
+<<
+>>
+>>>
+==
+===
+!=
+!==
+>
+<
+>=
+<=
+&&
+||
+!
+.
+[
+?
+:
+,
+;
+(
+{
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/docker-compose.yml b/plugins/Customizer/vendor/matthiasmullie/minify/docker-compose.yml
new file mode 100644
index 00000000..5413e24b
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/docker-compose.yml
@@ -0,0 +1,31 @@
+version: '2.1'
+services:
+ php:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ volumes:
+ - ./src:/var/www/src
+ - ./data:/var/www/data
+ - ./tests:/var/www/tests
+ - ./phpunit.xml.dist:/var/www/phpunit.xml.dist
+ '7.2':
+ extends: php
+ build:
+ args:
+ version: 7.2-cli
+ '7.1':
+ extends: php
+ build:
+ args:
+ version: 7.1-cli
+ '7.0':
+ extends: php
+ build:
+ args:
+ version: 7.0-cli
+ '5.6':
+ extends: php
+ build:
+ args:
+ version: 5.6-cli
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/src/CSS.php b/plugins/Customizer/vendor/matthiasmullie/minify/src/CSS.php
new file mode 100644
index 00000000..e92bb300
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/src/CSS.php
@@ -0,0 +1,736 @@
+<?php
+/**
+ * CSS Minifier
+ *
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
+ *
+ * @author Matthias Mullie <minify@mullie.eu>
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
+ * @license MIT License
+ */
+
+namespace MatthiasMullie\Minify;
+
+use MatthiasMullie\Minify\Exceptions\FileImportException;
+use MatthiasMullie\PathConverter\ConverterInterface;
+use MatthiasMullie\PathConverter\Converter;
+
+/**
+ * CSS minifier
+ *
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
+ *
+ * @package Minify
+ * @author Matthias Mullie <minify@mullie.eu>
+ * @author Tijs Verkoyen <minify@verkoyen.eu>
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
+ * @license MIT License
+ */
+class CSS extends Minify
+{
+ /**
+ * @var int maximum inport size in kB
+ */
+ protected $maxImportSize = 5;
+
+ /**
+ * @var string[] valid import extensions
+ */
+ protected $importExtensions = array(
+ 'gif' => 'data:image/gif',
+ 'png' => 'data:image/png',
+ 'jpe' => 'data:image/jpeg',
+ 'jpg' => 'data:image/jpeg',
+ 'jpeg' => 'data:image/jpeg',
+ 'svg' => 'data:image/svg+xml',
+ 'woff' => 'data:application/x-font-woff',
+ 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff',
+ 'xbm' => 'image/x-xbitmap',
+ );
+
+ /**
+ * Set the maximum size if files to be imported.
+ *
+ * Files larger than this size (in kB) will not be imported into the CSS.
+ * Importing files into the CSS as data-uri will save you some connections,
+ * but we should only import relatively small decorative images so that our
+ * CSS file doesn't get too bulky.
+ *
+ * @param int $size Size in kB
+ */
+ public function setMaxImportSize($size)
+ {
+ $this->maxImportSize = $size;
+ }
+
+ /**
+ * Set the type of extensions to be imported into the CSS (to save network
+ * connections).
+ * Keys of the array should be the file extensions & respective values
+ * should be the data type.
+ *
+ * @param string[] $extensions Array of file extensions
+ */
+ public function setImportExtensions(array $extensions)
+ {
+ $this->importExtensions = $extensions;
+ }
+
+ /**
+ * Move any import statements to the top.
+ *
+ * @param string $content Nearly finished CSS content
+ *
+ * @return string
+ */
+ protected function moveImportsToTop($content)
+ {
+ if (preg_match_all('/(;?)(@import (?<url>url\()?(?P<quotes>["\']?).+?(?P=quotes)(?(url)\)));?/', $content, $matches)) {
+ // remove from content
+ foreach ($matches[0] as $import) {
+ $content = str_replace($import, '', $content);
+ }
+
+ // add to top
+ $content = implode(';', $matches[2]).';'.trim($content, ';');
+ }
+
+ return $content;
+ }
+
+ /**
+ * Combine CSS from import statements.
+ *
+ * @import's will be loaded and their content merged into the original file,
+ * to save HTTP requests.
+ *
+ * @param string $source The file to combine imports for
+ * @param string $content The CSS content to combine imports for
+ * @param string[] $parents Parent paths, for circular reference checks
+ *
+ * @return string
+ *
+ * @throws FileImportException
+ */
+ protected function combineImports($source, $content, $parents)
+ {
+ $importRegexes = array(
+ // @import url(xxx)
+ '/
+ # import statement
+ @import
+
+ # whitespace
+ \s+
+
+ # open url()
+ url\(
+
+ # (optional) open path enclosure
+ (?P<quotes>["\']?)
+
+ # fetch path
+ (?P<path>.+?)
+
+ # (optional) close path enclosure
+ (?P=quotes)
+
+ # close url()
+ \)
+
+ # (optional) trailing whitespace
+ \s*
+
+ # (optional) media statement(s)
+ (?P<media>[^;]*)
+
+ # (optional) trailing whitespace
+ \s*
+
+ # (optional) closing semi-colon
+ ;?
+
+ /ix',
+
+ // @import 'xxx'
+ '/
+
+ # import statement
+ @import
+
+ # whitespace
+ \s+
+
+ # open path enclosure
+ (?P<quotes>["\'])
+
+ # fetch path
+ (?P<path>.+?)
+
+ # close path enclosure
+ (?P=quotes)
+
+ # (optional) trailing whitespace
+ \s*
+
+ # (optional) media statement(s)
+ (?P<media>[^;]*)
+
+ # (optional) trailing whitespace
+ \s*
+
+ # (optional) closing semi-colon
+ ;?
+
+ /ix',
+ );
+
+ // find all relative imports in css
+ $matches = array();
+ foreach ($importRegexes as $importRegex) {
+ if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) {
+ $matches = array_merge($matches, $regexMatches);
+ }
+ }
+
+ $search = array();
+ $replace = array();
+
+ // loop the matches
+ foreach ($matches as $match) {
+ // get the path for the file that will be imported
+ $importPath = dirname($source).'/'.$match['path'];
+
+ // only replace the import with the content if we can grab the
+ // content of the file
+ if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) {
+ continue;
+ }
+
+ // check if current file was not imported previously in the same
+ // import chain.
+ if (in_array($importPath, $parents)) {
+ throw new FileImportException('Failed to import file "'.$importPath.'": circular reference detected.');
+ }
+
+ // grab referenced file & minify it (which may include importing
+ // yet other @import statements recursively)
+ $minifier = new static($importPath);
+ $importContent = $minifier->execute($source, $parents);
+
+ // check if this is only valid for certain media
+ if (!empty($match['media'])) {
+ $importContent = '@media '.$match['media'].'{'.$importContent.'}';
+ }
+
+ // add to replacement array
+ $search[] = $match[0];
+ $replace[] = $importContent;
+ }
+
+ // replace the import statements
+ return str_replace($search, $replace, $content);
+ }
+
+ /**
+ * Import files into the CSS, base64-ized.
+ *
+ * @url(image.jpg) images will be loaded and their content merged into the
+ * original file, to save HTTP requests.
+ *
+ * @param string $source The file to import files for
+ * @param string $content The CSS content to import files for
+ *
+ * @return string
+ */
+ protected function importFiles($source, $content)
+ {
+ $regex = '/url\((["\']?)(.+?)\\1\)/i';
+ if ($this->importExtensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
+ $search = array();
+ $replace = array();
+
+ // loop the matches
+ foreach ($matches as $match) {
+ $extension = substr(strrchr($match[2], '.'), 1);
+ if ($extension && !array_key_exists($extension, $this->importExtensions)) {
+ continue;
+ }
+
+ // get the path for the file that will be imported
+ $path = $match[2];
+ $path = dirname($source).'/'.$path;
+
+ // only replace the import with the content if we're able to get
+ // the content of the file, and it's relatively small
+ if ($this->canImportFile($path) && $this->canImportBySize($path)) {
+ // grab content && base64-ize
+ $importContent = $this->load($path);
+ $importContent = base64_encode($importContent);
+
+ // build replacement
+ $search[] = $match[0];
+ $replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')';
+ }
+ }
+
+ // replace the import statements
+ $content = str_replace($search, $replace, $content);
+ }
+
+ return $content;
+ }
+
+ /**
+ * Minify the data.
+ * Perform CSS optimizations.
+ *
+ * @param string[optional] $path Path to write the data to
+ * @param string[] $parents Parent paths, for circular reference checks
+ *
+ * @return string The minified data
+ */
+ public function execute($path = null, $parents = array())
+ {
+ $content = '';
+
+ // loop CSS data (raw data and files)
+ foreach ($this->data as $source => $css) {
+ /*
+ * Let's first take out strings & comments, since we can't just
+ * remove whitespace anywhere. If whitespace occurs inside a string,
+ * we should leave it alone. E.g.:
+ * p { content: "a test" }
+ */
+ $this->extractStrings();
+ $this->stripComments();
+ $css = $this->replace($css);
+
+ $css = $this->stripWhitespace($css);
+ $css = $this->shortenHex($css);
+ $css = $this->shortenZeroes($css);
+ $css = $this->shortenFontWeights($css);
+ $css = $this->stripEmptyTags($css);
+
+ // restore the string we've extracted earlier
+ $css = $this->restoreExtractedData($css);
+
+ $source = is_int($source) ? '' : $source;
+ $parents = $source ? array_merge($parents, array($source)) : $parents;
+ $css = $this->combineImports($source, $css, $parents);
+ $css = $this->importFiles($source, $css);
+
+ /*
+ * If we'll save to a new path, we'll have to fix the relative paths
+ * to be relative no longer to the source file, but to the new path.
+ * If we don't write to a file, fall back to same path so no
+ * conversion happens (because we still want it to go through most
+ * of the move code, which also addresses url() & @import syntax...)
+ */
+ $converter = $this->getPathConverter($source, $path ?: $source);
+ $css = $this->move($converter, $css);
+
+ // combine css
+ $content .= $css;
+ }
+
+ $content = $this->moveImportsToTop($content);
+
+ return $content;
+ }
+
+ /**
+ * Moving a css file should update all relative urls.
+ * Relative references (e.g. ../images/image.gif) in a certain css file,
+ * will have to be updated when a file is being saved at another location
+ * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper).
+ *
+ * @param ConverterInterface $converter Relative path converter
+ * @param string $content The CSS content to update relative urls for
+ *
+ * @return string
+ */
+ protected function move(ConverterInterface $converter, $content)
+ {
+ /*
+ * Relative path references will usually be enclosed by url(). @import
+ * is an exception, where url() is not necessary around the path (but is
+ * allowed).
+ * This *could* be 1 regular expression, where both regular expressions
+ * in this array are on different sides of a |. But we're using named
+ * patterns in both regexes, the same name on both regexes. This is only
+ * possible with a (?J) modifier, but that only works after a fairly
+ * recent PCRE version. That's why I'm doing 2 separate regular
+ * expressions & combining the matches after executing of both.
+ */
+ $relativeRegexes = array(
+ // url(xxx)
+ '/
+ # open url()
+ url\(
+
+ \s*
+
+ # open path enclosure
+ (?P<quotes>["\'])?
+
+ # fetch path
+ (?P<path>.+?)
+
+ # close path enclosure
+ (?(quotes)(?P=quotes))
+
+ \s*
+
+ # close url()
+ \)
+
+ /ix',
+
+ // @import "xxx"
+ '/
+ # import statement
+ @import
+
+ # whitespace
+ \s+
+
+ # we don\'t have to check for @import url(), because the
+ # condition above will already catch these
+
+ # open path enclosure
+ (?P<quotes>["\'])
+
+ # fetch path
+ (?P<path>.+?)
+
+ # close path enclosure
+ (?P=quotes)
+
+ /ix',
+ );
+
+ // find all relative urls in css
+ $matches = array();
+ foreach ($relativeRegexes as $relativeRegex) {
+ if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) {
+ $matches = array_merge($matches, $regexMatches);
+ }
+ }
+
+ $search = array();
+ $replace = array();
+
+ // loop all urls
+ foreach ($matches as $match) {
+ // determine if it's a url() or an @import match
+ $type = (strpos($match[0], '@import') === 0 ? 'import' : 'url');
+
+ $url = $match['path'];
+ if ($this->canImportByPath($url)) {
+ // attempting to interpret GET-params makes no sense, so let's discard them for awhile
+ $params = strrchr($url, '?');
+ $url = $params ? substr($url, 0, -strlen($params)) : $url;
+
+ // fix relative url
+ $url = $converter->convert($url);
+
+ // now that the path has been converted, re-apply GET-params
+ $url .= $params;
+ }
+
+ /*
+ * Urls with control characters above 0x7e should be quoted.
+ * According to Mozilla's parser, whitespace is only allowed at the
+ * end of unquoted urls.
+ * Urls with `)` (as could happen with data: uris) should also be
+ * quoted to avoid being confused for the url() closing parentheses.
+ * And urls with a # have also been reported to cause issues.
+ * Urls with quotes inside should also remain escaped.
+ *
+ * @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation
+ * @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378
+ * @see https://github.com/matthiasmullie/minify/issues/193
+ */
+ $url = trim($url);
+ if (preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $url)) {
+ $url = $match['quotes'] . $url . $match['quotes'];
+ }
+
+ // build replacement
+ $search[] = $match[0];
+ if ($type === 'url') {
+ $replace[] = 'url('.$url.')';
+ } elseif ($type === 'import') {
+ $replace[] = '@import "'.$url.'"';
+ }
+ }
+
+ // replace urls
+ return str_replace($search, $replace, $content);
+ }
+
+ /**
+ * Shorthand hex color codes.
+ * #FF0000 -> #F00.
+ *
+ * @param string $content The CSS content to shorten the hex color codes for
+ *
+ * @return string
+ */
+ protected function shortenHex($content)
+ {
+ $content = preg_replace('/(?<=[: ])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?=[; }])/i', '#$1$2$3', $content);
+
+ // we can shorten some even more by replacing them with their color name
+ $colors = array(
+ '#F0FFFF' => 'azure',
+ '#F5F5DC' => 'beige',
+ '#A52A2A' => 'brown',
+ '#FF7F50' => 'coral',
+ '#FFD700' => 'gold',
+ '#808080' => 'gray',
+ '#008000' => 'green',
+ '#4B0082' => 'indigo',
+ '#FFFFF0' => 'ivory',
+ '#F0E68C' => 'khaki',
+ '#FAF0E6' => 'linen',
+ '#800000' => 'maroon',
+ '#000080' => 'navy',
+ '#808000' => 'olive',
+ '#CD853F' => 'peru',
+ '#FFC0CB' => 'pink',
+ '#DDA0DD' => 'plum',
+ '#800080' => 'purple',
+ '#F00' => 'red',
+ '#FA8072' => 'salmon',
+ '#A0522D' => 'sienna',
+ '#C0C0C0' => 'silver',
+ '#FFFAFA' => 'snow',
+ '#D2B48C' => 'tan',
+ '#FF6347' => 'tomato',
+ '#EE82EE' => 'violet',
+ '#F5DEB3' => 'wheat',
+ );
+
+ return preg_replace_callback(
+ '/(?<=[: ])('.implode(array_keys($colors), '|').')(?=[; }])/i',
+ function ($match) use ($colors) {
+ return $colors[strtoupper($match[0])];
+ },
+ $content
+ );
+ }
+
+ /**
+ * Shorten CSS font weights.
+ *
+ * @param string $content The CSS content to shorten the font weights for
+ *
+ * @return string
+ */
+ protected function shortenFontWeights($content)
+ {
+ $weights = array(
+ 'normal' => 400,
+ 'bold' => 700,
+ );
+
+ $callback = function ($match) use ($weights) {
+ return $match[1].$weights[$match[2]];
+ };
+
+ return preg_replace_callback('/(font-weight\s*:\s*)('.implode('|', array_keys($weights)).')(?=[;}])/', $callback, $content);
+ }
+
+ /**
+ * Shorthand 0 values to plain 0, instead of e.g. -0em.
+ *
+ * @param string $content The CSS content to shorten the zero values for
+ *
+ * @return string
+ */
+ protected function shortenZeroes($content)
+ {
+ // we don't want to strip units in `calc()` expressions:
+ // `5px - 0px` is valid, but `5px - 0` is not
+ // `10px * 0` is valid (equates to 0), and so is `10 * 0px`, but
+ // `10 * 0` is invalid
+ // best to just leave `calc()`s alone, even if they could be optimized
+ // (which is a whole other undertaking, where units & order of
+ // operations all need to be considered...)
+ $calcs = $this->findCalcs($content);
+ $content = str_replace($calcs, array_keys($calcs), $content);
+
+ // reusable bits of code throughout these regexes:
+ // before & after are used to make sure we don't match lose unintended
+ // 0-like values (e.g. in #000, or in http://url/1.0)
+ // units can be stripped from 0 values, or used to recognize non 0
+ // values (where wa may be able to strip a .0 suffix)
+ $before = '(?<=[:(, ])';
+ $after = '(?=[ ,);}])';
+ $units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)';
+
+ // strip units after zeroes (0px -> 0)
+ // NOTE: it should be safe to remove all units for a 0 value, but in
+ // practice, Webkit (especially Safari) seems to stumble over at least
+ // 0%, potentially other units as well. Only stripping 'px' for now.
+ // @see https://github.com/matthiasmullie/minify/issues/60
+ $content = preg_replace('/'.$before.'(-?0*(\.0+)?)(?<=0)px'.$after.'/', '\\1', $content);
+
+ // strip 0-digits (.0 -> 0)
+ $content = preg_replace('/'.$before.'\.0+'.$units.'?'.$after.'/', '0\\1', $content);
+ // strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px
+ $content = preg_replace('/'.$before.'(-?[0-9]+\.[0-9]+)0+'.$units.'?'.$after.'/', '\\1\\2', $content);
+ // strip trailing 0: 50.00 -> 50, 50.00px -> 50px
+ $content = preg_replace('/'.$before.'(-?[0-9]+)\.0+'.$units.'?'.$after.'/', '\\1\\2', $content);
+ // strip leading 0: 0.1 -> .1, 01.1 -> 1.1
+ $content = preg_replace('/'.$before.'(-?)0+([0-9]*\.[0-9]+)'.$units.'?'.$after.'/', '\\1\\2\\3', $content);
+
+ // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0)
+ $content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content);
+
+ // IE doesn't seem to understand a unitless flex-basis value (correct -
+ // it goes against the spec), so let's add it in again (make it `%`,
+ // which is only 1 char: 0%, 0px, 0 anything, it's all just the same)
+ // @see https://developer.mozilla.org/nl/docs/Web/CSS/flex
+ $content = preg_replace('/flex:([0-9]+\s[0-9]+\s)0([;\}])/', 'flex:${1}0%${2}', $content);
+ $content = preg_replace('/flex-basis:0([;\}])/', 'flex-basis:0%${1}', $content);
+
+ // restore `calc()` expressions
+ $content = str_replace(array_keys($calcs), $calcs, $content);
+
+ return $content;
+ }
+
+ /**
+ * Strip empty tags from source code.
+ *
+ * @param string $content
+ *
+ * @return string
+ */
+ protected function stripEmptyTags($content)
+ {
+ $content = preg_replace('/(?<=^)[^\{\};]+\{\s*\}/', '', $content);
+ $content = preg_replace('/(?<=(\}|;))[^\{\};]+\{\s*\}/', '', $content);
+
+ return $content;
+ }
+
+ /**
+ * Strip comments from source code.
+ */
+ protected function stripComments()
+ {
+ $this->registerPattern('/\/\*.*?\*\//s', '');
+ }
+
+ /**
+ * Strip whitespace.
+ *
+ * @param string $content The CSS content to strip the whitespace for
+ *
+ * @return string
+ */
+ protected function stripWhitespace($content)
+ {
+ // remove leading & trailing whitespace
+ $content = preg_replace('/^\s*/m', '', $content);
+ $content = preg_replace('/\s*$/m', '', $content);
+
+ // replace newlines with a single space
+ $content = preg_replace('/\s+/', ' ', $content);
+
+ // remove whitespace around meta characters
+ // inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex
+ $content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content);
+ $content = preg_replace('/([\[(:])\s+/', '$1', $content);
+ $content = preg_replace('/\s+([\]\)])/', '$1', $content);
+ $content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content);
+
+ // whitespace around + and - can only be stripped inside some pseudo-
+ // classes, like `:nth-child(3+2n)`
+ // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or
+ // selectors like `div.weird- p`
+ $pseudos = array('nth-child', 'nth-last-child', 'nth-last-of-type', 'nth-of-type');
+ $content = preg_replace('/:('.implode('|', $pseudos).')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content);
+
+ // remove semicolon/whitespace followed by closing bracket
+ $content = str_replace(';}', '}', $content);
+
+ return trim($content);
+ }
+
+ /**
+ * Find all `calc()` occurrences.
+ *
+ * @param string $content The CSS content to find `calc()`s in.
+ *
+ * @return string[]
+ */
+ protected function findCalcs($content)
+ {
+ $results = array();
+ preg_match_all('/calc(\(.+?)(?=$|;|calc\()/', $content, $matches, PREG_SET_ORDER);
+
+ foreach ($matches as $match) {
+ $length = strlen($match[1]);
+ $expr = '';
+ $opened = 0;
+
+ for ($i = 0; $i < $length; $i++) {
+ $char = $match[1][$i];
+ $expr .= $char;
+ if ($char === '(') {
+ $opened++;
+ } elseif ($char === ')' && --$opened === 0) {
+ break;
+ }
+ }
+
+ $results['calc('.count($results).')'] = 'calc'.$expr;
+ }
+
+ return $results;
+ }
+
+ /**
+ * Check if file is small enough to be imported.
+ *
+ * @param string $path The path to the file
+ *
+ * @return bool
+ */
+ protected function canImportBySize($path)
+ {
+ return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024;
+ }
+
+ /**
+ * Check if file a file can be imported, going by the path.
+ *
+ * @param string $path
+ *
+ * @return bool
+ */
+ protected function canImportByPath($path)
+ {
+ return preg_match('/^(data:|https?:|\\/)/', $path) === 0;
+ }
+
+ /**
+ * Return a converter to update relative paths to be relative to the new
+ * destination.
+ *
+ * @param string $source
+ * @param string $target
+ *
+ * @return ConverterInterface
+ */
+ protected function getPathConverter($source, $target)
+ {
+ return new Converter($source, $target);
+ }
+}
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/src/Exception.php b/plugins/Customizer/vendor/matthiasmullie/minify/src/Exception.php
new file mode 100644
index 00000000..d03898f0
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/src/Exception.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * Base Exception
+ *
+ * @deprecated Use Exceptions\BasicException instead
+ *
+ * @author Matthias Mullie <minify@mullie.eu>
+ */
+namespace MatthiasMullie\Minify;
+
+/**
+ * Base Exception Class
+ * @deprecated Use Exceptions\BasicException instead
+ *
+ * @package Minify
+ * @author Matthias Mullie <minify@mullie.eu>
+ */
+abstract class Exception extends \Exception
+{
+}
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/BasicException.php b/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/BasicException.php
new file mode 100644
index 00000000..af5e81bc
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/BasicException.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Basic exception
+ *
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
+ *
+ * @author Matthias Mullie <minify@mullie.eu>
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
+ * @license MIT License
+ */
+namespace MatthiasMullie\Minify\Exceptions;
+
+use MatthiasMullie\Minify\Exception;
+
+/**
+ * Basic Exception Class
+ *
+ * @package Minify\Exception
+ * @author Matthias Mullie <minify@mullie.eu>
+ */
+abstract class BasicException extends Exception
+{
+}
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php b/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php
new file mode 100644
index 00000000..912a2c90
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * File Import Exception
+ *
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
+ *
+ * @author Matthias Mullie <minify@mullie.eu>
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
+ * @license MIT License
+ */
+namespace MatthiasMullie\Minify\Exceptions;
+
+/**
+ * File Import Exception Class
+ *
+ * @package Minify\Exception
+ * @author Matthias Mullie <minify@mullie.eu>
+ */
+class FileImportException extends BasicException
+{
+}
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/IOException.php b/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/IOException.php
new file mode 100644
index 00000000..b172eb48
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/IOException.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * IO Exception
+ *
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
+ *
+ * @author Matthias Mullie <minify@mullie.eu>
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
+ * @license MIT License
+ */
+namespace MatthiasMullie\Minify\Exceptions;
+
+/**
+ * IO Exception Class
+ *
+ * @package Minify\Exception
+ * @author Matthias Mullie <minify@mullie.eu>
+ */
+class IOException extends BasicException
+{
+}
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/src/JS.php b/plugins/Customizer/vendor/matthiasmullie/minify/src/JS.php
new file mode 100644
index 00000000..361afe35
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/src/JS.php
@@ -0,0 +1,598 @@
+<?php
+/**
+ * JavaScript minifier
+ *
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
+ *
+ * @author Matthias Mullie <minify@mullie.eu>
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
+ * @license MIT License
+ */
+namespace MatthiasMullie\Minify;
+
+/**
+ * JavaScript Minifier Class
+ *
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
+ *
+ * @package Minify
+ * @author Matthias Mullie <minify@mullie.eu>
+ * @author Tijs Verkoyen <minify@verkoyen.eu>
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
+ * @license MIT License
+ */
+class JS extends Minify
+{
+ /**
+ * Var-matching regex based on http://stackoverflow.com/a/9337047/802993.
+ *
+ * Note that regular expressions using that bit must have the PCRE_UTF8
+ * pattern modifier (/u) set.
+ *
+ * @var string
+ */
+ const REGEX_VARIABLE = '\b[$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}][$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}0-9\x{0300}-\x{036f}\x{0483}-\x{0487}\x{0591}-\x{05bd}\x{05bf}\x{05c1}\x{05c2}\x{05c4}\x{05c5}\x{05c7}\x{0610}-\x{061a}\x{064b}-\x{0669}\x{0670}\x{06d6}-\x{06dc}\x{06df}-\x{06e4}\x{06e7}\x{06e8}\x{06ea}-\x{06ed}\x{06f0}-\x{06f9}\x{0711}\x{0730}-\x{074a}\x{07a6}-\x{07b0}\x{07c0}-\x{07c9}\x{07eb}-\x{07f3}\x{0816}-\x{0819}\x{081b}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082d}\x{0859}-\x{085b}\x{08e4}-\x{08fe}\x{0900}-\x{0903}\x{093a}-\x{093c}\x{093e}-\x{094f}\x{0951}-\x{0957}\x{0962}\x{0963}\x{0966}-\x{096f}\x{0981}-\x{0983}\x{09bc}\x{09be}-\x{09c4}\x{09c7}\x{09c8}\x{09cb}-\x{09cd}\x{09d7}\x{09e2}\x{09e3}\x{09e6}-\x{09ef}\x{0a01}-\x{0a03}\x{0a3c}\x{0a3e}-\x{0a42}\x{0a47}\x{0a48}\x{0a4b}-\x{0a4d}\x{0a51}\x{0a66}-\x{0a71}\x{0a75}\x{0a81}-\x{0a83}\x{0abc}\x{0abe}-\x{0ac5}\x{0ac7}-\x{0ac9}\x{0acb}-\x{0acd}\x{0ae2}\x{0ae3}\x{0ae6}-\x{0aef}\x{0b01}-\x{0b03}\x{0b3c}\x{0b3e}-\x{0b44}\x{0b47}\x{0b48}\x{0b4b}-\x{0b4d}\x{0b56}\x{0b57}\x{0b62}\x{0b63}\x{0b66}-\x{0b6f}\x{0b82}\x{0bbe}-\x{0bc2}\x{0bc6}-\x{0bc8}\x{0bca}-\x{0bcd}\x{0bd7}\x{0be6}-\x{0bef}\x{0c01}-\x{0c03}\x{0c3e}-\x{0c44}\x{0c46}-\x{0c48}\x{0c4a}-\x{0c4d}\x{0c55}\x{0c56}\x{0c62}\x{0c63}\x{0c66}-\x{0c6f}\x{0c82}\x{0c83}\x{0cbc}\x{0cbe}-\x{0cc4}\x{0cc6}-\x{0cc8}\x{0cca}-\x{0ccd}\x{0cd5}\x{0cd6}\x{0ce2}\x{0ce3}\x{0ce6}-\x{0cef}\x{0d02}\x{0d03}\x{0d3e}-\x{0d44}\x{0d46}-\x{0d48}\x{0d4a}-\x{0d4d}\x{0d57}\x{0d62}\x{0d63}\x{0d66}-\x{0d6f}\x{0d82}\x{0d83}\x{0dca}\x{0dcf}-\x{0dd4}\x{0dd6}\x{0dd8}-\x{0ddf}\x{0df2}\x{0df3}\x{0e31}\x{0e34}-\x{0e3a}\x{0e47}-\x{0e4e}\x{0e50}-\x{0e59}\x{0eb1}\x{0eb4}-\x{0eb9}\x{0ebb}\x{0ebc}\x{0ec8}-\x{0ecd}\x{0ed0}-\x{0ed9}\x{0f18}\x{0f19}\x{0f20}-\x{0f29}\x{0f35}\x{0f37}\x{0f39}\x{0f3e}\x{0f3f}\x{0f71}-\x{0f84}\x{0f86}\x{0f87}\x{0f8d}-\x{0f97}\x{0f99}-\x{0fbc}\x{0fc6}\x{102b}-\x{103e}\x{1040}-\x{1049}\x{1056}-\x{1059}\x{105e}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106d}\x{1071}-\x{1074}\x{1082}-\x{108d}\x{108f}-\x{109d}\x{135d}-\x{135f}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17d3}\x{17dd}\x{17e0}-\x{17e9}\x{180b}-\x{180d}\x{1810}-\x{1819}\x{18a9}\x{1920}-\x{192b}\x{1930}-\x{193b}\x{1946}-\x{194f}\x{19b0}-\x{19c0}\x{19c8}\x{19c9}\x{19d0}-\x{19d9}\x{1a17}-\x{1a1b}\x{1a55}-\x{1a5e}\x{1a60}-\x{1a7c}\x{1a7f}-\x{1a89}\x{1a90}-\x{1a99}\x{1b00}-\x{1b04}\x{1b34}-\x{1b44}\x{1b50}-\x{1b59}\x{1b6b}-\x{1b73}\x{1b80}-\x{1b82}\x{1ba1}-\x{1bad}\x{1bb0}-\x{1bb9}\x{1be6}-\x{1bf3}\x{1c24}-\x{1c37}\x{1c40}-\x{1c49}\x{1c50}-\x{1c59}\x{1cd0}-\x{1cd2}\x{1cd4}-\x{1ce8}\x{1ced}\x{1cf2}-\x{1cf4}\x{1dc0}-\x{1de6}\x{1dfc}-\x{1dff}\x{200c}\x{200d}\x{203f}\x{2040}\x{2054}\x{20d0}-\x{20dc}\x{20e1}\x{20e5}-\x{20f0}\x{2cef}-\x{2cf1}\x{2d7f}\x{2de0}-\x{2dff}\x{302a}-\x{302f}\x{3099}\x{309a}\x{a620}-\x{a629}\x{a66f}\x{a674}-\x{a67d}\x{a69f}\x{a6f0}\x{a6f1}\x{a802}\x{a806}\x{a80b}\x{a823}-\x{a827}\x{a880}\x{a881}\x{a8b4}-\x{a8c4}\x{a8d0}-\x{a8d9}\x{a8e0}-\x{a8f1}\x{a900}-\x{a909}\x{a926}-\x{a92d}\x{a947}-\x{a953}\x{a980}-\x{a983}\x{a9b3}-\x{a9c0}\x{a9d0}-\x{a9d9}\x{aa29}-\x{aa36}\x{aa43}\x{aa4c}\x{aa4d}\x{aa50}-\x{aa59}\x{aa7b}\x{aab0}\x{aab2}-\x{aab4}\x{aab7}\x{aab8}\x{aabe}\x{aabf}\x{aac1}\x{aaeb}-\x{aaef}\x{aaf5}\x{aaf6}\x{abe3}-\x{abea}\x{abec}\x{abed}\x{abf0}-\x{abf9}\x{fb1e}\x{fe00}-\x{fe0f}\x{fe20}-\x{fe26}\x{fe33}\x{fe34}\x{fe4d}-\x{fe4f}\x{ff10}-\x{ff19}\x{ff3f}]*\b';
+
+ /**
+ * Full list of JavaScript reserved words.
+ * Will be loaded from /data/js/keywords_reserved.txt.
+ *
+ * @see https://mathiasbynens.be/notes/reserved-keywords
+ *
+ * @var string[]
+ */
+ protected $keywordsReserved = array();
+
+ /**
+ * List of JavaScript reserved words that accept a <variable, value, ...>
+ * after them. Some end of lines are not the end of a statement, like with
+ * these keywords.
+ *
+ * E.g.: we shouldn't insert a ; after this else
+ * else
+ * console.log('this is quite fine')
+ *
+ * Will be loaded from /data/js/keywords_before.txt
+ *
+ * @var string[]
+ */
+ protected $keywordsBefore = array();
+
+ /**
+ * List of JavaScript reserved words that accept a <variable, value, ...>
+ * before them. Some end of lines are not the end of a statement, like when
+ * continued by one of these keywords on the newline.
+ *
+ * E.g.: we shouldn't insert a ; before this instanceof
+ * variable
+ * instanceof String
+ *
+ * Will be loaded from /data/js/keywords_after.txt
+ *
+ * @var string[]
+ */
+ protected $keywordsAfter = array();
+
+ /**
+ * List of all JavaScript operators.
+ *
+ * Will be loaded from /data/js/operators.txt
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
+ *
+ * @var string[]
+ */
+ protected $operators = array();
+
+ /**
+ * List of JavaScript operators that accept a <variable, value, ...> after
+ * them. Some end of lines are not the end of a statement, like with these
+ * operators.
+ *
+ * Note: Most operators are fine, we've only removed ++ and --.
+ * ++ & -- have to be joined with the value they're in-/decrementing.
+ *
+ * Will be loaded from /data/js/operators_before.txt
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
+ *
+ * @var string[]
+ */
+ protected $operatorsBefore = array();
+
+ /**
+ * List of JavaScript operators that accept a <variable, value, ...> before
+ * them. Some end of lines are not the end of a statement, like when
+ * continued by one of these operators on the newline.
+ *
+ * Note: Most operators are fine, we've only removed ), ], ++, --, ! and ~.
+ * There can't be a newline separating ! or ~ and whatever it is negating.
+ * ++ & -- have to be joined with the value they're in-/decrementing.
+ * ) & ] are "special" in that they have lots or usecases. () for example
+ * is used for function calls, for grouping, in if () and for (), ...
+ *
+ * Will be loaded from /data/js/operators_after.txt
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
+ *
+ * @var string[]
+ */
+ protected $operatorsAfter = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct()
+ {
+ call_user_func_array(array('parent', '__construct'), func_get_args());
+
+ $dataDir = __DIR__.'/../data/js/';
+ $options = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES;
+ $this->keywordsReserved = file($dataDir.'keywords_reserved.txt', $options);
+ $this->keywordsBefore = file($dataDir.'keywords_before.txt', $options);
+ $this->keywordsAfter = file($dataDir.'keywords_after.txt', $options);
+ $this->operators = file($dataDir.'operators.txt', $options);
+ $this->operatorsBefore = file($dataDir.'operators_before.txt', $options);
+ $this->operatorsAfter = file($dataDir.'operators_after.txt', $options);
+ }
+
+ /**
+ * Minify the data.
+ * Perform JS optimizations.
+ *
+ * @param string[optional] $path Path to write the data to
+ *
+ * @return string The minified data
+ */
+ public function execute($path = null)
+ {
+ $content = '';
+
+ /*
+ * Let's first take out strings, comments and regular expressions.
+ * All of these can contain JS code-like characters, and we should make
+ * sure any further magic ignores anything inside of these.
+ *
+ * Consider this example, where we should not strip any whitespace:
+ * var str = "a test";
+ *
+ * Comments will be removed altogether, strings and regular expressions
+ * will be replaced by placeholder text, which we'll restore later.
+ */
+ $this->extractStrings('\'"`');
+ $this->stripComments();
+ $this->extractRegex();
+
+ // loop files
+ foreach ($this->data as $source => $js) {
+ // take out strings, comments & regex (for which we've registered
+ // the regexes just a few lines earlier)
+ $js = $this->replace($js);
+
+ $js = $this->propertyNotation($js);
+ $js = $this->shortenBools($js);
+ $js = $this->stripWhitespace($js);
+
+ // combine js: separating the scripts by a ;
+ $content .= $js.";";
+ }
+
+ // clean up leftover `;`s from the combination of multiple scripts
+ $content = ltrim($content, ';');
+ $content = (string) substr($content, 0, -1);
+
+ /*
+ * Earlier, we extracted strings & regular expressions and replaced them
+ * with placeholder text. This will restore them.
+ */
+ $content = $this->restoreExtractedData($content);
+
+ return $content;
+ }
+
+ /**
+ * Strip comments from source code.
+ */
+ protected function stripComments()
+ {
+ // single-line comments
+ $this->registerPattern('/\/\/.*$/m', '');
+
+ // multi-line comments
+ $this->registerPattern('/\/\*.*?\*\//s', '');
+ }
+
+ /**
+ * JS can have /-delimited regular expressions, like: /ab+c/.match(string).
+ *
+ * The content inside the regex can contain characters that may be confused
+ * for JS code: e.g. it could contain whitespace it needs to match & we
+ * don't want to strip whitespace in there.
+ *
+ * The regex can be pretty simple: we don't have to care about comments,
+ * (which also use slashes) because stripComments() will have stripped those
+ * already.
+ *
+ * This method will replace all string content with simple REGEX#
+ * placeholder text, so we've rid all regular expressions from characters
+ * that may be misinterpreted. Original regex content will be saved in
+ * $this->extracted and after doing all other minifying, we can restore the
+ * original content via restoreRegex()
+ */
+ protected function extractRegex()
+ {
+ // PHP only supports $this inside anonymous functions since 5.4
+ $minifier = $this;
+ $callback = function ($match) use ($minifier) {
+ $count = count($minifier->extracted);
+ $placeholder = '"'.$count.'"';
+ $minifier->extracted[$placeholder] = $match[0];
+
+ return $placeholder;
+ };
+
+ // match all chars except `/` and `\`
+ // `\` is allowed though, along with whatever char follows (which is the
+ // one being escaped)
+ // this should allow all chars, except for an unescaped `/` (= the one
+ // closing the regex)
+ // then also ignore bare `/` inside `[]`, where they don't need to be
+ // escaped: anything inside `[]` can be ignored safely
+ $pattern = '\\/(?:[^\\[\\/\\\\\n\r]+|(?:\\\\.)+|(?:\\[(?:[^\\]\\\\\n\r]+|(?:\\\\.)+)+\\])+)++\\/[gimuy]*';
+
+ // a regular expression can only be followed by a few operators or some
+ // of the RegExp methods (a `\` followed by a variable or value is
+ // likely part of a division, not a regex)
+ $keywords = array('do', 'in', 'new', 'else', 'throw', 'yield', 'delete', 'return', 'typeof');
+ $before = '([=:,;\+\-\*\/\}\(\{\[&\|!]|^|'.implode('|', $keywords).')\s*';
+ $propertiesAndMethods = array(
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Properties_2
+ 'constructor',
+ 'flags',
+ 'global',
+ 'ignoreCase',
+ 'multiline',
+ 'source',
+ 'sticky',
+ 'unicode',
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Methods_2
+ 'compile(',
+ 'exec(',
+ 'test(',
+ 'toSource(',
+ 'toString(',
+ );
+ $delimiters = array_fill(0, count($propertiesAndMethods), '/');
+ $propertiesAndMethods = array_map('preg_quote', $propertiesAndMethods, $delimiters);
+ $after = '(?=\s*([\.,;\)\}&\|+]|\/\/|$|\.('.implode('|', $propertiesAndMethods).')))';
+ $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback);
+
+ // regular expressions following a `)` are rather annoying to detect...
+ // quite often, `/` after `)` is a division operator & if it happens to
+ // be followed by another one (or a comment), it is likely to be
+ // confused for a regular expression
+ // however, it's perfectly possible for a regex to follow a `)`: after
+ // a single-line `if()`, `while()`, ... statement, for example
+ // since, when they occur like that, they're always the start of a
+ // statement, there's only a limited amount of ways they can be useful:
+ // by calling the regex methods directly
+ // if a regex following `)` is not followed by `.<property or method>`,
+ // it's quite likely not a regex
+ $before = '\)\s*';
+ $after = '(?=\s*\.('.implode('|', $propertiesAndMethods).'))';
+ $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback);
+
+ // 1 more edge case: a regex can be followed by a lot more operators or
+ // keywords if there's a newline (ASI) in between, where the operator
+ // actually starts a new statement
+ // (https://github.com/matthiasmullie/minify/issues/56)
+ $operators = $this->getOperatorsForRegex($this->operatorsBefore, '/');
+ $operators += $this->getOperatorsForRegex($this->keywordsReserved, '/');
+ $after = '(?=\s*\n\s*('.implode('|', $operators).'))';
+ $this->registerPattern('/'.$pattern.$after.'/', $callback);
+ }
+
+ /**
+ * Strip whitespace.
+ *
+ * We won't strip *all* whitespace, but as much as possible. The thing that
+ * we'll preserve are newlines we're unsure about.
+ * JavaScript doesn't require statements to be terminated with a semicolon.
+ * It will automatically fix missing semicolons with ASI (automatic semi-
+ * colon insertion) at the end of line causing errors (without semicolon.)
+ *
+ * Because it's sometimes hard to tell if a newline is part of a statement
+ * that should be terminated or not, we'll just leave some of them alone.
+ *
+ * @param string $content The content to strip the whitespace for
+ *
+ * @return string
+ */
+ protected function stripWhitespace($content)
+ {
+ // uniform line endings, make them all line feed
+ $content = str_replace(array("\r\n", "\r"), "\n", $content);
+
+ // collapse all non-line feed whitespace into a single space
+ $content = preg_replace('/[^\S\n]+/', ' ', $content);
+
+ // strip leading & trailing whitespace
+ $content = str_replace(array(" \n", "\n "), "\n", $content);
+
+ // collapse consecutive line feeds into just 1
+ $content = preg_replace('/\n+/', "\n", $content);
+
+ $operatorsBefore = $this->getOperatorsForRegex($this->operatorsBefore, '/');
+ $operatorsAfter = $this->getOperatorsForRegex($this->operatorsAfter, '/');
+ $operators = $this->getOperatorsForRegex($this->operators, '/');
+ $keywordsBefore = $this->getKeywordsForRegex($this->keywordsBefore, '/');
+ $keywordsAfter = $this->getKeywordsForRegex($this->keywordsAfter, '/');
+
+ // strip whitespace that ends in (or next line begin with) an operator
+ // that allows statements to be broken up over multiple lines
+ unset($operatorsBefore['+'], $operatorsBefore['-'], $operatorsAfter['+'], $operatorsAfter['-']);
+ $content = preg_replace(
+ array(
+ '/('.implode('|', $operatorsBefore).')\s+/',
+ '/\s+('.implode('|', $operatorsAfter).')/',
+ ), '\\1', $content
+ );
+
+ // make sure + and - can't be mistaken for, or joined into ++ and --
+ $content = preg_replace(
+ array(
+ '/(?<![\+\-])\s*([\+\-])(?![\+\-])/',
+ '/(?<![\+\-])([\+\-])\s*(?![\+\-])/',
+ ), '\\1', $content
+ );
+
+ // collapse whitespace around reserved words into single space
+ $content = preg_replace('/(^|[;\}\s])\K('.implode('|', $keywordsBefore).')\s+/', '\\2 ', $content);
+ $content = preg_replace('/\s+('.implode('|', $keywordsAfter).')(?=([;\{\s]|$))/', ' \\1', $content);
+
+ /*
+ * We didn't strip whitespace after a couple of operators because they
+ * could be used in different contexts and we can't be sure it's ok to
+ * strip the newlines. However, we can safely strip any non-line feed
+ * whitespace that follows them.
+ */
+ $operatorsDiffBefore = array_diff($operators, $operatorsBefore);
+ $operatorsDiffAfter = array_diff($operators, $operatorsAfter);
+ $content = preg_replace('/('.implode('|', $operatorsDiffBefore).')[^\S\n]+/', '\\1', $content);
+ $content = preg_replace('/[^\S\n]+('.implode('|', $operatorsDiffAfter).')/', '\\1', $content);
+
+ /*
+ * Whitespace after `return` can be omitted in a few occasions
+ * (such as when followed by a string or regex)
+ * Same for whitespace in between `)` and `{`, or between `{` and some
+ * keywords.
+ */
+ $content = preg_replace('/\breturn\s+(["\'\/\+\-])/', 'return$1', $content);
+ $content = preg_replace('/\)\s+\{/', '){', $content);
+ $content = preg_replace('/}\n(else|catch|finally)\b/', '}$1', $content);
+
+ /*
+ * Get rid of double semicolons, except where they can be used like:
+ * "for(v=1,_=b;;)", "for(v=1;;v++)" or "for(;;ja||(ja=true))".
+ * I'll safeguard these double semicolons inside for-loops by
+ * temporarily replacing them with an invalid condition: they won't have
+ * a double semicolon and will be easy to spot to restore afterwards.
+ */
+ $content = preg_replace('/\bfor\(([^;]*);;([^;]*)\)/', 'for(\\1;-;\\2)', $content);
+ $content = preg_replace('/;+/', ';', $content);
+ $content = preg_replace('/\bfor\(([^;]*);-;([^;]*)\)/', 'for(\\1;;\\2)', $content);
+
+ /*
+ * Next, we'll be removing all semicolons where ASI kicks in.
+ * for-loops however, can have an empty body (ending in only a
+ * semicolon), like: `for(i=1;i<3;i++);`, of `for(i in list);`
+ * Here, nothing happens during the loop; it's just used to keep
+ * increasing `i`. With that ; omitted, the next line would be expected
+ * to be the for-loop's body... Same goes for while loops.
+ * I'm going to double that semicolon (if any) so after the next line,
+ * which strips semicolons here & there, we're still left with this one.
+ */
+ $content = preg_replace('/(for\([^;\{]*;[^;\{]*;[^;\{]*\));(\}|$)/s', '\\1;;\\2', $content);
+ $content = preg_replace('/(for\([^;\{]+\s+in\s+[^;\{]+\));(\}|$)/s', '\\1;;\\2', $content);
+ /*
+ * Below will also keep `;` after a `do{}while();` along with `while();`
+ * While these could be stripped after do-while, detecting this
+ * distinction is cumbersome, so I'll play it safe and make sure `;`
+ * after any kind of `while` is kept.
+ */
+ $content = preg_replace('/(while\([^;\{]+\));(\}|$)/s', '\\1;;\\2', $content);
+
+ /*
+ * We also can't strip empty else-statements. Even though they're
+ * useless and probably shouldn't be in the code in the first place, we
+ * shouldn't be stripping the `;` that follows it as it breaks the code.
+ * We can just remove those useless else-statements completely.
+ *
+ * @see https://github.com/matthiasmullie/minify/issues/91
+ */
+ $content = preg_replace('/else;/s', '', $content);
+
+ /*
+ * We also don't really want to terminate statements followed by closing
+ * curly braces (which we've ignored completely up until now) or end-of-
+ * script: ASI will kick in here & we're all about minifying.
+ * Semicolons at beginning of the file don't make any sense either.
+ */
+ $content = preg_replace('/;(\}|$)/s', '\\1', $content);
+ $content = ltrim($content, ';');
+
+ // get rid of remaining whitespace af beginning/end
+ return trim($content);
+ }
+
+ /**
+ * We'll strip whitespace around certain operators with regular expressions.
+ * This will prepare the given array by escaping all characters.
+ *
+ * @param string[] $operators
+ * @param string $delimiter
+ *
+ * @return string[]
+ */
+ protected function getOperatorsForRegex(array $operators, $delimiter = '/')
+ {
+ // escape operators for use in regex
+ $delimiters = array_fill(0, count($operators), $delimiter);
+ $escaped = array_map('preg_quote', $operators, $delimiters);
+
+ $operators = array_combine($operators, $escaped);
+
+ // ignore + & - for now, they'll get special treatment
+ unset($operators['+'], $operators['-']);
+
+ // dot can not just immediately follow a number; it can be confused for
+ // decimal point, or calling a method on it, e.g. 42 .toString()
+ $operators['.'] = '(?<![0-9]\s)\.';
+
+ // don't confuse = with other assignment shortcuts (e.g. +=)
+ $chars = preg_quote('+-*\=<>%&|', $delimiter);
+ $operators['='] = '(?<!['.$chars.'])\=';
+
+ return $operators;
+ }
+
+ /**
+ * We'll strip whitespace around certain keywords with regular expressions.
+ * This will prepare the given array by escaping all characters.
+ *
+ * @param string[] $keywords
+ * @param string $delimiter
+ *
+ * @return string[]
+ */
+ protected function getKeywordsForRegex(array $keywords, $delimiter = '/')
+ {
+ // escape keywords for use in regex
+ $delimiter = array_fill(0, count($keywords), $delimiter);
+ $escaped = array_map('preg_quote', $keywords, $delimiter);
+
+ // add word boundaries
+ array_walk($keywords, function ($value) {
+ return '\b'.$value.'\b';
+ });
+
+ $keywords = array_combine($keywords, $escaped);
+
+ return $keywords;
+ }
+
+ /**
+ * Replaces all occurrences of array['key'] by array.key.
+ *
+ * @param string $content
+ *
+ * @return string
+ */
+ protected function propertyNotation($content)
+ {
+ // PHP only supports $this inside anonymous functions since 5.4
+ $minifier = $this;
+ $keywords = $this->keywordsReserved;
+ $callback = function ($match) use ($minifier, $keywords) {
+ $property = trim($minifier->extracted[$match[1]], '\'"');
+
+ /*
+ * Check if the property is a reserved keyword. In this context (as
+ * property of an object literal/array) it shouldn't matter, but IE8
+ * freaks out with "Expected identifier".
+ */
+ if (in_array($property, $keywords)) {
+ return $match[0];
+ }
+
+ /*
+ * See if the property is in a variable-like format (e.g.
+ * array['key-here'] can't be replaced by array.key-here since '-'
+ * is not a valid character there.
+ */
+ if (!preg_match('/^'.$minifier::REGEX_VARIABLE.'$/u', $property)) {
+ return $match[0];
+ }
+
+ return '.'.$property;
+ };
+
+ /*
+ * Figure out if previous character is a variable name (of the array
+ * we want to use property notation on) - this is to make sure
+ * standalone ['value'] arrays aren't confused for keys-of-an-array.
+ * We can (and only have to) check the last character, because PHP's
+ * regex implementation doesn't allow unfixed-length look-behind
+ * assertions.
+ */
+ preg_match('/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar);
+ $previousChar = $previousChar[1];
+
+ /*
+ * Make sure word preceding the ['value'] is not a keyword, e.g.
+ * return['x']. Because -again- PHP's regex implementation doesn't allow
+ * unfixed-length look-behind assertions, I'm just going to do a lot of
+ * separate look-behind assertions, one for each keyword.
+ */
+ $keywords = $this->getKeywordsForRegex($keywords);
+ $keywords = '(?<!'.implode(')(?<!', $keywords).')';
+
+ return preg_replace_callback('/(?<='.$previousChar.'|\])'.$keywords.'\[\s*(([\'"])[0-9]+\\2)\s*\]/u', $callback, $content);
+ }
+
+ /**
+ * Replaces true & false by !0 and !1.
+ *
+ * @param string $content
+ *
+ * @return string
+ */
+ protected function shortenBools($content)
+ {
+ /*
+ * 'true' or 'false' could be used as property names (which may be
+ * followed by whitespace) - we must not replace those!
+ * Since PHP doesn't allow variable-length (to account for the
+ * whitespace) lookbehind assertions, I need to capture the leading
+ * character and check if it's a `.`
+ */
+ $callback = function ($match) {
+ if (trim($match[1]) === '.') {
+ return $match[0];
+ }
+
+ return $match[1].($match[2] === 'true' ? '!0' : '!1');
+ };
+ $content = preg_replace_callback('/(^|.\s*)\b(true|false)\b(?!:)/', $callback, $content);
+
+ // for(;;) is exactly the same as while(true), but shorter :)
+ $content = preg_replace('/\bwhile\(!0\){/', 'for(;;){', $content);
+
+ // now make sure we didn't turn any do ... while(true) into do ... for(;;)
+ preg_match_all('/\bdo\b/', $content, $dos, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
+
+ // go backward to make sure positional offsets aren't altered when $content changes
+ $dos = array_reverse($dos);
+ foreach ($dos as $do) {
+ $offsetDo = $do[0][1];
+
+ // find all `while` (now `for`) following `do`: one of those must be
+ // associated with the `do` and be turned back into `while`
+ preg_match_all('/\bfor\(;;\)/', $content, $whiles, PREG_OFFSET_CAPTURE | PREG_SET_ORDER, $offsetDo);
+ foreach ($whiles as $while) {
+ $offsetWhile = $while[0][1];
+
+ $open = substr_count($content, '{', $offsetDo, $offsetWhile - $offsetDo);
+ $close = substr_count($content, '}', $offsetDo, $offsetWhile - $offsetDo);
+ if ($open === $close) {
+ // only restore `while` if amount of `{` and `}` are the same;
+ // otherwise, that `for` isn't associated with this `do`
+ $content = substr_replace($content, 'while(!0)', $offsetWhile, strlen('for(;;)'));
+ break;
+ }
+ }
+ }
+
+ return $content;
+ }
+}
diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/src/Minify.php b/plugins/Customizer/vendor/matthiasmullie/minify/src/Minify.php
new file mode 100644
index 00000000..bfbc1ad2
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/minify/src/Minify.php
@@ -0,0 +1,459 @@
+<?php
+/**
+ * Abstract minifier class
+ *
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
+ *
+ * @author Matthias Mullie <minify@mullie.eu>
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
+ * @license MIT License
+ */
+namespace MatthiasMullie\Minify;
+
+use MatthiasMullie\Minify\Exceptions\IOException;
+use Psr\Cache\CacheItemInterface;
+
+/**
+ * Abstract minifier class.
+ *
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
+ *
+ * @package Minify
+ * @author Matthias Mullie <minify@mullie.eu>
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
+ * @license MIT License
+ */
+abstract class Minify
+{
+ /**
+ * The data to be minified.
+ *
+ * @var string[]
+ */
+ protected $data = array();
+
+ /**
+ * Array of patterns to match.
+ *
+ * @var string[]
+ */
+ protected $patterns = array();
+
+ /**
+ * This array will hold content of strings and regular expressions that have
+ * been extracted from the JS source code, so we can reliably match "code",
+ * without having to worry about potential "code-like" characters inside.
+ *
+ * @var string[]
+ */
+ public $extracted = array();
+
+ /**
+ * Init the minify class - optionally, code may be passed along already.
+ */
+ public function __construct(/* $data = null, ... */)
+ {
+ // it's possible to add the source through the constructor as well ;)
+ if (func_num_args()) {
+ call_user_func_array(array($this, 'add'), func_get_args());
+ }
+ }
+
+ /**
+ * Add a file or straight-up code to be minified.
+ *
+ * @param string|string[] $data
+ *
+ * @return static
+ */
+ public function add($data /* $data = null, ... */)
+ {
+ // bogus "usage" of parameter $data: scrutinizer warns this variable is
+ // not used (we're using func_get_args instead to support overloading),
+ // but it still needs to be defined because it makes no sense to have
+ // this function without argument :)
+ $args = array($data) + func_get_args();
+
+ // this method can be overloaded
+ foreach ($args as $data) {
+ if (is_array($data)) {
+ call_user_func_array(array($this, 'add'), $data);
+ continue;
+ }
+
+ // redefine var
+ $data = (string) $data;
+
+ // load data
+ $value = $this->load($data);
+ $key = ($data != $value) ? $data : count($this->data);
+
+ // replace CR linefeeds etc.
+ // @see https://github.com/matthiasmullie/minify/pull/139
+ $value = str_replace(array("\r\n", "\r"), "\n", $value);
+
+ // store data
+ $this->data[$key] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Minify the data & (optionally) saves it to a file.
+ *
+ * @param string[optional] $path Path to write the data to
+ *
+ * @return string The minified data
+ */
+ public function minify($path = null)
+ {
+ $content = $this->execute($path);
+
+ // save to path
+ if ($path !== null) {
+ $this->save($content, $path);
+ }
+
+ return $content;
+ }
+
+ /**
+ * Minify & gzip the data & (optionally) saves it to a file.
+ *
+ * @param string[optional] $path Path to write the data to
+ * @param int[optional] $level Compression level, from 0 to 9
+ *
+ * @return string The minified & gzipped data
+ */
+ public function gzip($path = null, $level = 9)
+ {
+ $content = $this->execute($path);
+ $content = gzencode($content, $level, FORCE_GZIP);
+
+ // save to path
+ if ($path !== null) {
+ $this->save($content, $path);
+ }
+
+ return $content;
+ }
+
+ /**
+ * Minify the data & write it to a CacheItemInterface object.
+ *
+ * @param CacheItemInterface $item Cache item to write the data to
+ *
+ * @return CacheItemInterface Cache item with the minifier data
+ */
+ public function cache(CacheItemInterface $item)
+ {
+ $content = $this->execute();
+ $item->set($content);
+
+ return $item;
+ }
+
+ /**
+ * Minify the data.
+ *
+ * @param string[optional] $path Path to write the data to
+ *
+ * @return string The minified data
+ */
+ abstract public function execute($path = null);
+
+ /**
+ * Load data.
+ *
+ * @param string $data Either a path to a file or the content itself
+ *
+ * @return string
+ */
+ protected function load($data)
+ {
+ // check if the data is a file
+ if ($this->canImportFile($data)) {
+ $data = file_get_contents($data);
+
+ // strip BOM, if any
+ if (substr($data, 0, 3) == "\xef\xbb\xbf") {
+ $data = substr($data, 3);
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Save to file.
+ *
+ * @param string $content The minified data
+ * @param string $path The path to save the minified data to
+ *
+ * @throws IOException
+ */
+ protected function save($content, $path)
+ {
+ $handler = $this->openFileForWriting($path);
+
+ $this->writeToFile($handler, $content);
+
+ @fclose($handler);
+ }
+
+ /**
+ * Register a pattern to execute against the source content.
+ *
+ * @param string $pattern PCRE pattern
+ * @param string|callable $replacement Replacement value for matched pattern
+ */
+ protected function registerPattern($pattern, $replacement = '')
+ {
+ // study the pattern, we'll execute it more than once
+ $pattern .= 'S';
+
+ $this->patterns[] = array($pattern, $replacement);
+ }
+
+ /**
+ * We can't "just" run some regular expressions against JavaScript: it's a
+ * complex language. E.g. having an occurrence of // xyz would be a comment,
+ * unless it's used within a string. Of you could have something that looks
+ * like a 'string', but inside a comment.
+ * The only way to accurately replace these pieces is to traverse the JS one
+ * character at a time and try to find whatever starts first.
+ *
+ * @param string $content The content to replace patterns in
+ *
+ * @return string The (manipulated) content
+ */
+ protected function replace($content)
+ {
+ $processed = '';
+ $positions = array_fill(0, count($this->patterns), -1);
+ $matches = array();
+
+ while ($content) {
+ // find first match for all patterns
+ foreach ($this->patterns as $i => $pattern) {
+ list($pattern, $replacement) = $pattern;
+
+ // we can safely ignore patterns for positions we've unset earlier,
+ // because we know these won't show up anymore
+ if (!isset($positions[$i])) {
+ continue;
+ }
+
+ // no need to re-run matches that are still in the part of the
+ // content that hasn't been processed
+ if ($positions[$i] >= 0) {
+ continue;
+ }
+
+ $match = null;
+ if (preg_match($pattern, $content, $match, PREG_OFFSET_CAPTURE)) {
+ $matches[$i] = $match;
+
+ // we'll store the match position as well; that way, we
+ // don't have to redo all preg_matches after changing only
+ // the first (we'll still know where those others are)
+ $positions[$i] = $match[0][1];
+ } else {
+ // if the pattern couldn't be matched, there's no point in
+ // executing it again in later runs on this same content;
+ // ignore this one until we reach end of content
+ unset($matches[$i], $positions[$i]);
+ }
+ }
+
+ // no more matches to find: everything's been processed, break out
+ if (!$matches) {
+ $processed .= $content;
+ break;
+ }
+
+ // see which of the patterns actually found the first thing (we'll
+ // only want to execute that one, since we're unsure if what the
+ // other found was not inside what the first found)
+ $discardLength = min($positions);
+ $firstPattern = array_search($discardLength, $positions);
+ $match = $matches[$firstPattern][0][0];
+
+ // execute the pattern that matches earliest in the content string
+ list($pattern, $replacement) = $this->patterns[$firstPattern];
+ $replacement = $this->replacePattern($pattern, $replacement, $content);
+
+ // figure out which part of the string was unmatched; that's the
+ // part we'll execute the patterns on again next
+ $content = (string) substr($content, $discardLength);
+ $unmatched = (string) substr($content, strpos($content, $match) + strlen($match));
+
+ // move the replaced part to $processed and prepare $content to
+ // again match batch of patterns against
+ $processed .= substr($replacement, 0, strlen($replacement) - strlen($unmatched));
+ $content = $unmatched;
+
+ // first match has been replaced & that content is to be left alone,
+ // the next matches will start after this replacement, so we should
+ // fix their offsets
+ foreach ($positions as $i => $position) {
+ $positions[$i] -= $discardLength + strlen($match);
+ }
+ }
+
+ return $processed;
+ }
+
+ /**
+ * This is where a pattern is matched against $content and the matches
+ * are replaced by their respective value.
+ * This function will be called plenty of times, where $content will always
+ * move up 1 character.
+ *
+ * @param string $pattern Pattern to match
+ * @param string|callable $replacement Replacement value
+ * @param string $content Content to match pattern against
+ *
+ * @return string
+ */
+ protected function replacePattern($pattern, $replacement, $content)
+ {
+ if (is_callable($replacement)) {
+ return preg_replace_callback($pattern, $replacement, $content, 1, $count);
+ } else {
+ return preg_replace($pattern, $replacement, $content, 1, $count);
+ }
+ }
+
+ /**
+ * Strings are a pattern we need to match, in order to ignore potential
+ * code-like content inside them, but we just want all of the string
+ * content to remain untouched.
+ *
+ * This method will replace all string content with simple STRING#
+ * placeholder text, so we've rid all strings from characters that may be
+ * misinterpreted. Original string content will be saved in $this->extracted
+ * and after doing all other minifying, we can restore the original content
+ * via restoreStrings().
+ *
+ * @param string[optional] $chars
+ * @param string[optional] $placeholderPrefix
+ */
+ protected function extractStrings($chars = '\'"', $placeholderPrefix = '')
+ {
+ // PHP only supports $this inside anonymous functions since 5.4
+ $minifier = $this;
+ $callback = function ($match) use ($minifier, $placeholderPrefix) {
+ // check the second index here, because the first always contains a quote
+ if ($match[2] === '') {
+ /*
+ * Empty strings need no placeholder; they can't be confused for
+ * anything else anyway.
+ * But we still needed to match them, for the extraction routine
+ * to skip over this particular string.
+ */
+ return $match[0];
+ }
+
+ $count = count($minifier->extracted);
+ $placeholder = $match[1].$placeholderPrefix.$count.$match[1];
+ $minifier->extracted[$placeholder] = $match[1].$match[2].$match[1];
+
+ return $placeholder;
+ };
+
+ /*
+ * The \\ messiness explained:
+ * * Don't count ' or " as end-of-string if it's escaped (has backslash
+ * in front of it)
+ * * Unless... that backslash itself is escaped (another leading slash),
+ * in which case it's no longer escaping the ' or "
+ * * So there can be either no backslash, or an even number
+ * * multiply all of that times 4, to account for the escaping that has
+ * to be done to pass the backslash into the PHP string without it being
+ * considered as escape-char (times 2) and to get it in the regex,
+ * escaped (times 2)
+ */
+ $this->registerPattern('/(['.$chars.'])(.*?(?<!\\\\)(\\\\\\\\)*+)\\1/s', $callback);
+ }
+
+ /**
+ * This method will restore all extracted data (strings, regexes) that were
+ * replaced with placeholder text in extract*(). The original content was
+ * saved in $this->extracted.
+ *
+ * @param string $content
+ *
+ * @return string
+ */
+ protected function restoreExtractedData($content)
+ {
+ if (!$this->extracted) {
+ // nothing was extracted, nothing to restore
+ return $content;
+ }
+
+ $content = strtr($content, $this->extracted);
+
+ $this->extracted = array();
+
+ return $content;
+ }
+
+ /**
+ * Check if the path is a regular file and can be read.
+ *
+ * @param string $path
+ *
+ * @return bool
+ */
+ protected function canImportFile($path)
+ {
+ $parsed = parse_url($path);
+ if (
+ // file is elsewhere
+ isset($parsed['host']) ||
+ // file responds to queries (may change, or need to bypass cache)
+ isset($parsed['query'])
+ ) {
+ return false;
+ }
+
+ return strlen($path) < PHP_MAXPATHLEN && @is_file($path) && is_readable($path);
+ }
+
+ /**
+ * Attempts to open file specified by $path for writing.
+ *
+ * @param string $path The path to the file
+ *
+ * @return resource Specifier for the target file
+ *
+ * @throws IOException
+ */
+ protected function openFileForWriting($path)
+ {
+ if (($handler = @fopen($path, 'w')) === false) {
+ throw new IOException('The file "'.$path.'" could not be opened for writing. Check if PHP has enough permissions.');
+ }
+
+ return $handler;
+ }
+
+ /**
+ * Attempts to write $content to the file specified by $handler. $path is used for printing exceptions.
+ *
+ * @param resource $handler The resource to write to
+ * @param string $content The content to write
+ * @param string $path The path to the file (for exception printing only)
+ *
+ * @throws IOException
+ */
+ protected function writeToFile($handler, $content, $path = '')
+ {
+ if (($result = @fwrite($handler, $content)) === false || ($result < strlen($content))) {
+ throw new IOException('The file "'.$path.'" could not be written to. Check your disk space and file permissions.');
+ }
+ }
+}
diff --git a/plugins/Customizer/vendor/matthiasmullie/path-converter/LICENSE b/plugins/Customizer/vendor/matthiasmullie/path-converter/LICENSE
new file mode 100644
index 00000000..491295ad
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/path-converter/LICENSE
@@ -0,0 +1,18 @@
+Copyright (c) 2015 Matthias Mullie
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/plugins/Customizer/vendor/matthiasmullie/path-converter/composer.json b/plugins/Customizer/vendor/matthiasmullie/path-converter/composer.json
new file mode 100644
index 00000000..1cb6a4c5
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/path-converter/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "matthiasmullie/path-converter",
+ "type": "library",
+ "description": "Relative path converter",
+ "keywords": ["relative", "path", "converter", "paths"],
+ "homepage": "http://github.com/matthiasmullie/path-converter",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Matthias Mullie",
+ "homepage": "http://www.mullie.eu",
+ "email": "pathconverter@mullie.eu",
+ "role": "Developer"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0",
+ "ext-pcre": "*"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "autoload": {
+ "psr-4": {
+ "MatthiasMullie\\PathConverter\\": "src/"
+ }
+ }
+}
diff --git a/plugins/Customizer/vendor/matthiasmullie/path-converter/src/Converter.php b/plugins/Customizer/vendor/matthiasmullie/path-converter/src/Converter.php
new file mode 100644
index 00000000..519d3c84
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/path-converter/src/Converter.php
@@ -0,0 +1,196 @@
+<?php
+
+namespace MatthiasMullie\PathConverter;
+
+/**
+ * Convert paths relative from 1 file to another.
+ *
+ * E.g.
+ * ../../images/icon.jpg relative to /css/imports/icons.css
+ * becomes
+ * ../images/icon.jpg relative to /css/minified.css
+ *
+ * Please report bugs on https://github.com/matthiasmullie/path-converter/issues
+ *
+ * @author Matthias Mullie <pathconverter@mullie.eu>
+ * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
+ * @license MIT License
+ */
+class Converter implements ConverterInterface
+{
+ /**
+ * @var string
+ */
+ protected $from;
+
+ /**
+ * @var string
+ */
+ protected $to;
+
+ /**
+ * @param string $from The original base path (directory, not file!)
+ * @param string $to The new base path (directory, not file!)
+ * @param string $root Root directory (defaults to `getcwd`)
+ */
+ public function __construct($from, $to, $root = '')
+ {
+ $shared = $this->shared($from, $to);
+ if ($shared === '') {
+ // when both paths have nothing in common, one of them is probably
+ // absolute while the other is relative
+ $root = $root ?: getcwd();
+ $from = strpos($from, $root) === 0 ? $from : preg_replace('/\/+/', '/', $root.'/'.$from);
+ $to = strpos($to, $root) === 0 ? $to : preg_replace('/\/+/', '/', $root.'/'.$to);
+
+ // or traveling the tree via `..`
+ // attempt to resolve path, or assume it's fine if it doesn't exist
+ $from = @realpath($from) ?: $from;
+ $to = @realpath($to) ?: $to;
+ }
+
+ $from = $this->dirname($from);
+ $to = $this->dirname($to);
+
+ $from = $this->normalize($from);
+ $to = $this->normalize($to);
+
+ $this->from = $from;
+ $this->to = $to;
+ }
+
+ /**
+ * Normalize path.
+ *
+ * @param string $path
+ *
+ * @return string
+ */
+ protected function normalize($path)
+ {
+ // deal with different operating systems' directory structure
+ $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/');
+
+ /*
+ * Example:
+ * /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif
+ * to
+ * /home/forkcms/frontend/core/layout/images/img.gif
+ */
+ do {
+ $path = preg_replace('/[^\/]+(?<!\.\.)\/\.\.\//', '', $path, -1, $count);
+ } while ($count);
+
+ return $path;
+ }
+
+ /**
+ * Figure out the shared path of 2 locations.
+ *
+ * Example:
+ * /home/forkcms/frontend/core/layout/images/img.gif
+ * and
+ * /home/forkcms/frontend/cache/minified_css
+ * share
+ * /home/forkcms/frontend
+ *
+ * @param string $path1
+ * @param string $path2
+ *
+ * @return string
+ */
+ protected function shared($path1, $path2)
+ {
+ // $path could theoretically be empty (e.g. no path is given), in which
+ // case it shouldn't expand to array(''), which would compare to one's
+ // root /
+ $path1 = $path1 ? explode('/', $path1) : array();
+ $path2 = $path2 ? explode('/', $path2) : array();
+
+ $shared = array();
+
+ // compare paths & strip identical ancestors
+ foreach ($path1 as $i => $chunk) {
+ if (isset($path2[$i]) && $path1[$i] == $path2[$i]) {
+ $shared[] = $chunk;
+ } else {
+ break;
+ }
+ }
+
+ return implode('/', $shared);
+ }
+
+ /**
+ * Convert paths relative from 1 file to another.
+ *
+ * E.g.
+ * ../images/img.gif relative to /home/forkcms/frontend/core/layout/css
+ * should become:
+ * ../../core/layout/images/img.gif relative to
+ * /home/forkcms/frontend/cache/minified_css
+ *
+ * @param string $path The relative path that needs to be converted
+ *
+ * @return string The new relative path
+ */
+ public function convert($path)
+ {
+ // quit early if conversion makes no sense
+ if ($this->from === $this->to) {
+ return $path;
+ }
+
+ $path = $this->normalize($path);
+ // if we're not dealing with a relative path, just return absolute
+ if (strpos($path, '/') === 0) {
+ return $path;
+ }
+
+ // normalize paths
+ $path = $this->normalize($this->from.'/'.$path);
+
+ // strip shared ancestor paths
+ $shared = $this->shared($path, $this->to);
+ $path = mb_substr($path, mb_strlen($shared));
+ $to = mb_substr($this->to, mb_strlen($shared));
+
+ // add .. for every directory that needs to be traversed to new path
+ $to = str_repeat('../', count(array_filter(explode('/', $to))));
+
+ return $to.ltrim($path, '/');
+ }
+
+ /**
+ * Attempt to get the directory name from a path.
+ *
+ * @param string $path
+ *
+ * @return string
+ */
+ protected function dirname($path)
+ {
+ if (@is_file($path)) {
+ return dirname($path);
+ }
+
+ if (@is_dir($path)) {
+ return rtrim($path, '/');
+ }
+
+ // no known file/dir, start making assumptions
+
+ // ends in / = dir
+ if (mb_substr($path, -1) === '/') {
+ return rtrim($path, '/');
+ }
+
+ // has a dot in the name, likely a file
+ if (preg_match('/.*\..*$/', basename($path)) !== 0) {
+ return dirname($path);
+ }
+
+ // you're on your own here!
+ return $path;
+ }
+}
diff --git a/plugins/Customizer/vendor/matthiasmullie/path-converter/src/ConverterInterface.php b/plugins/Customizer/vendor/matthiasmullie/path-converter/src/ConverterInterface.php
new file mode 100644
index 00000000..dc1b7657
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/path-converter/src/ConverterInterface.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace MatthiasMullie\PathConverter;
+
+/**
+ * Convert file paths.
+ *
+ * Please report bugs on https://github.com/matthiasmullie/path-converter/issues
+ *
+ * @author Matthias Mullie <pathconverter@mullie.eu>
+ * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
+ * @license MIT License
+ */
+interface ConverterInterface
+{
+ /**
+ * Convert file paths.
+ *
+ * @param string $path The path to be converted
+ *
+ * @return string The new path
+ */
+ public function convert($path);
+}
diff --git a/plugins/Customizer/vendor/matthiasmullie/path-converter/src/NoConverter.php b/plugins/Customizer/vendor/matthiasmullie/path-converter/src/NoConverter.php
new file mode 100644
index 00000000..2fcfd0f2
--- /dev/null
+++ b/plugins/Customizer/vendor/matthiasmullie/path-converter/src/NoConverter.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace MatthiasMullie\PathConverter;
+
+/**
+ * Don't convert paths.
+ *
+ * Please report bugs on https://github.com/matthiasmullie/path-converter/issues
+ *
+ * @author Matthias Mullie <pathconverter@mullie.eu>
+ * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
+ * @license MIT License
+ */
+class NoConverter implements ConverterInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function convert($path)
+ {
+ return $path;
+ }
+}