diff --git a/.gitignore b/.gitignore index 8a6e2ab..6dbb891 100644 --- a/.gitignore +++ b/.gitignore @@ -9,9 +9,13 @@ vendor data/* !data/EXAMPLE/config.yaml !data/EXAMPLE/logo.png +!data/EXAMPLE/i_will_be_moved.txt # logs /logs # local conf /conf/*.local.yaml + +# export directory +/export \ No newline at end of file diff --git a/app/dependencies.php b/app/dependencies.php index 7c03fd0..0f46345 100644 --- a/app/dependencies.php +++ b/app/dependencies.php @@ -1,6 +1,7 @@ addDefinitions([ + FileExporter::class => function (ContainerInterface $c) { + $settings = $c->get('settings'); + $mailSettings = $settings['fileExporter'] ?? []; + + return new FileExporter($mailSettings); + } + ]); }; diff --git a/conf/language-de.default.yaml b/conf/language.de.yaml similarity index 90% rename from conf/language-de.default.yaml rename to conf/language.de.yaml index e526642..5a6bd64 100644 --- a/conf/language-de.default.yaml +++ b/conf/language.de.yaml @@ -18,3 +18,4 @@ button_send : Senden button_upload: Datei hochladen button_upload_replace: Datei ersetzen uploaded_file: Hochgeladene Dateien +upload_info: Die ausgewählte Datei wird beim Speichern oder Senden hochgeladen und geprüft \ No newline at end of file diff --git a/conf/language.default.yaml b/conf/language.default.yaml index 8a8fa1d..74fdda8 100644 --- a/conf/language.default.yaml +++ b/conf/language.default.yaml @@ -18,3 +18,4 @@ button_send : Send button_upload: Upload file button_upload_replace: Replace uploaded file uploaded_file: Saved file +upload_info: The selected file will be uploaded and checked after saving or sending the form \ No newline at end of file diff --git a/conf/settings.default.yaml b/conf/settings.default.yaml index 42b078a..037c4d6 100644 --- a/conf/settings.default.yaml +++ b/conf/settings.default.yaml @@ -7,3 +7,5 @@ settings: encryption: ~ username: ~ password: ~ + fileExporter: + dir: export \ No newline at end of file diff --git a/data/EXAMPLE/config.yaml b/data/EXAMPLE/config.yaml index 3d89456..18e1a76 100644 --- a/data/EXAMPLE/config.yaml +++ b/data/EXAMPLE/config.yaml @@ -9,6 +9,10 @@ meta: - recipient2@example.com cc: - fieldset0.fieldset_dynamic.email1 + export: i_will_be_moved.txt + saveButton: true + tooltip_style: 'border: 1px solid cyan' + language: de form: fieldset0: type: fieldset @@ -25,6 +29,11 @@ form: - 'Static elements' - 'Dynamic elements' - 'Fieldset columns' + hr0: + type: hr + column: is-full + color: '#f5f5f5' + height: 2 fieldset_static: type: fieldset label: 'Static form elements' @@ -62,6 +71,7 @@ form: validation: match: /[a-zA-Z0-9]/ required: false + tooltip: 'Optional input\nAllowed characters: a-Z, 0-9' numberinput1: type: numberinput label: Numberinput @@ -92,7 +102,7 @@ form: dropdown1: type: dropdown label: 'Dropdown' - default: 'Please choose' + empty_label: 'Please choose' choices: - 'Choice #1' - 'Choice #2' @@ -103,7 +113,7 @@ form: label: 'Checklist' choices: - 'Choice #1' - - 'Choice #2' + - 'Choice #2 [a nice link](https://www.cosmocode.de)' - 'Choice #3' upload1: type: upload diff --git a/data/EXAMPLE/i_will_be_moved.txt b/data/EXAMPLE/i_will_be_moved.txt new file mode 100644 index 0000000..8670833 --- /dev/null +++ b/data/EXAMPLE/i_will_be_moved.txt @@ -0,0 +1 @@ +With the default configuration i will be moved to 'export/' \ No newline at end of file diff --git a/doc/app.md b/doc/app.md index 4bf1c6e..96b8e25 100644 --- a/doc/app.md +++ b/doc/app.md @@ -2,5 +2,5 @@ All settings and message strings are defined in `conf/` -You can override them by copying `*.default.yaml` to `*.local.yaml` and adjusting the values. +You can override the defaults by copying `settings.default.yaml` and/or `language.default.yaml` to `*.local.yaml` and adjusting the values. Also see [Language Settings](meta.md#Translations) diff --git a/doc/formelements.md b/doc/formelements.md index 2d8f041..b6e20c2 100644 --- a/doc/formelements.md +++ b/doc/formelements.md @@ -21,6 +21,7 @@ Fieldsets group other form elements (including nested fieldsets). Options: * `children` _(required)_ - containing child form elements +* `tablestyle` _(optional)_ set table view true or false. This will color the rows like zebrastripes. Labels of the containing formElements are hidden. The tablehead row will be populated from the labels from the children of the first fieldset. If cells in the first row must be skipped then [Spacers](#spacers) can be used. * `toggle` _(optional)_ - the fieldset is disabled and hidden until the toggle condition is met * `field` - dotted path to the field whose value will be evaluated to match the toggle condition * `value` - required value to toggle the fieldset on @@ -107,6 +108,38 @@ Options: type: hidden value: "hidden value" ``` + + +### Spacer + +Representation of a empty table cell. Should be used in table fieldset (tableStyle: true). +Options: +* `label` _(required)_ - The label. If the spacer is inside the first fieldset of a table fieldset (tableStyle: true) then the label will be used +* `double` _(optional)_ - If set to true also skips cell below + +```yaml + : + type: spacer + label: "label" + double: true +``` + +### Horizontal line +Representation of a hr. + +Options: +* `column` _(optional)_ +* `color` _(optional)_ - The color of the hr +* `height` _(optional)_ - The height of the hr + +```yaml + : + type: hr + column: is-full + color: '#f5f5f5' + height: 2 +``` + ## Dynamic fields (user input) All dynamic fields have a value the user can enter. @@ -114,10 +147,12 @@ They are required by default. Options: * `validation` _(optional)_ - used to apply [validation](validation.md) +* `tooltip` _(optional)_ - Shows a hint for the form element. Also see [Tooltip styling](meta.md#Tooltips) ```yaml : type: + tooltip: 'A useful hint for this field.' validation: required: false match: /^regex_expression$/ @@ -243,10 +278,11 @@ Options: Representation of a select input. Options: -* `choices` _(required)_ - defines available options -* `default` _(optional)_ - a placeholder text shown if no value was chosen (e.g. "Please select"). **Note:** this is not a real option and has no value that could be saved. +* `choices` _(required)_ - defines available options. Markdown ist supported. +* `empty_label` _(optional)_ - a placeholder text shown if no value was chosen (e.g. "Please select"). **Note:** this is not a real option and has no value that could be saved. * `multiselect` _(optional)_ - enables selecting multiple options * `size` _(optional)_ - if multiselect is turned on this defines the number of rows shown +* `default` _(optional)_ : Preselects a choice. This is just triggered if the form was never saved before. **Preselect in toggles are not supported yet.** **BREAKING CHANGE until version 1.0.4 this parameter was used for empty_label** ```yaml : @@ -254,9 +290,10 @@ Options: label: dropdown label multiselect: true size: 3 - default: choose an option + empty_label: choose an option + default: 'first choice [a nice link](https://www.cosmocode.de)' choices: - - first choice + - first choice [a nice link](https://www.cosmocode.de) - second choice ``` @@ -266,8 +303,8 @@ Representation of a checkbox group. Options: * `alignment` _(optional)_ - sets the alignment of the checkboxes, possible values are `vertical` or `horizontal` (default) -* `choices` _(required)_ - defines available options/checkboxes - +* `choices` _(required)_ - defines available options/checkboxes. Markdown is supported. +* `default` _(optional)_ : Preselects a choice. This is just triggered if the form was never saved before. **Preselect in toggles are not supported yet.** ```yaml : type: checklist @@ -275,6 +312,7 @@ Options: alignment: vertical validation: required: false + default: 'First choice' choices: - First choice - Second choice diff --git a/doc/meta.md b/doc/meta.md index 937366f..98151dd 100644 --- a/doc/meta.md +++ b/doc/meta.md @@ -24,3 +24,53 @@ Your CSS file will be included after our basic styles, which are mostly plain [B ``` An email with user input and attached uploads will be sent to configured addresses. A copy can be sent to an address from an email field if you provide the fieldId(s) in the `cc` section. + +## Translations + +```yaml + language: de +``` + +The set language will be loaded from ./conf/language-{{language}}.yaml. + +At first ./conf/language.default.yaml gets loaded. + +If ./conf/language.local.yaml exists it will override all set strings from the default file. + +Finally ./conf/language-{{language}}.yaml. overrides all set strings from the previous files + + +## File export options + +```yaml + export: file_to_be_moved.txt +``` + +When the form email gets send the given file (in this case file_to_be_moved.txt) will be moved to the export directory. The export directory is set by default to ./export. It can be changed by overriding the default settings (./conf/settings.default.yaml): + Create / Edit the file ./conf/settings.local.yaml. + Add: + ```yaml + fileExporter: + dir : + ``` + + The directory is always relative from the projects root dir. + + ## Visibility Savebutton + + ```yaml + saveButton: true + ``` + +The visibilty of the save button can be toggled. +If this option is not set the button will be shown by default. + +## Tooltips + + ```yaml + tooltip_style: 'border: 1px solid cyan' + ``` + +This setting controls the tooltip button style attribute. + +Every tooltip for this form is affected. \ No newline at end of file diff --git a/public/css/app.css b/public/css/app.css index b204f0d..cbe328f 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -11,10 +11,6 @@ canvas { background: #ffffff; } -.fieldset-label { - font-size: 20pt; -} - .checkbox { padding-left: 4px; padding-right: 4px; @@ -24,8 +20,166 @@ canvas { display: none; } -/** Required so fieldsets dont destroy bulmas column system */ +/** Tooltips */ +.label { + display: inline-block; +} + +.tooltip-button { + border: 0; +} + +/** Required so fieldsets don't destroy Bulma's column system */ fieldset { display: contents; +} + +.fieldset-label { + font-size: 1.25rem; + font-weight: 500; +} + +.is-full .fieldset-label { + margin-bottom: 1rem; +} + +fieldset h3, +fieldset .subtitle { + font-size: 1.13rem; + font-weight: 400; + line-height: 1.25; +} + + +@media screen and (max-width: 768px), print { + .field.is-spacer label, + .field.is-spacer-doublelabel label { + display: none; + } +} + + +@media screen and (min-width: 769px), print { + .fieldset-label { + min-height: 3.75rem; + } + + .is-full .fieldset-label { + min-height: 1rem; + } + + .columns.is-multiline { + margin-bottom: 1rem; + } + + .columns.is-multiline .is-multiline { + margin-bottom: 0; + } + + + /** + + + + + spacer + + + + + */ -} \ No newline at end of file + .field.is-spacer { + height: 4.25em; + } + + .field.is-spacer-doublelabel { + height: 10em; + } + + /** + + + + + spacer in table design + + + + + */ + + .is-left-label .field.is-spacer { + height: 2.25em; /* input-height: 2.25 */ + } + + .is-left-label .field.is-spacer-doublelabel { + height: 6em; /* 2 x input-height: 2.25 + 2 x padding: 0.75*/ + } + + .is-left-label .field.is-spacer label, + .is-left-label .field.is-spacer-doublelabel label { + display: inline-block; + } + + + /** + + + + + label once + + + + + */ + + /* + + + width of cols + + + */ + + .is-left-label .column.is-half { + width: 33.3333%; + } + + .is-left-label fieldset:first-of-type .column.is-half { + width: 66.6666%; + } + + .is-left-label .column.is-one-third { + width: 25%; + } + + .is-left-label fieldset:first-of-type .column.is-one-third { + width: 50%; + } + + .is-left-label .column.is-one-quarter { + width: 20%; + } + + .is-left-label fieldset:first-of-type .column.is-one-quarter { + width: 40%; + } + + /* + + + background of cols + + + */ + + .is-left-label fieldset:nth-of-type(even) .column .is-multiline { + background-color: #F3F3F3; + } + + .is-left-label fieldset:first-of-type .fieldset-content .columns { + position: relative; + } + + .is-left-label fieldset:first-of-type .fieldset-content .columns::before { + content: ''; + position: absolute; + width: 50%; + height: 100%; + background-color: #F3F3F3; + border-right: 10px solid #FFFFFF; + } + + /* + + + position of col header (1. col) + + + */ + + .is-left-label fieldset:first-of-type .fieldset-label { + display: block; + width: 50%; + margin-right: 0; + margin-left: auto; + } + + /* + + + position of labels + width of input (1. col) + + + */ + + .is-left-label .column .field { + position: relative; + } + + .is-left-label label { + position: absolute; + left: -1000rem; + top: -1000rem; + } + + .is-left-label fieldset:first-of-type label { + left: 0; + top: 0; + width: 50%; + } + + .is-left-label fieldset:first-of-type .control { + width: 50%; + margin-right: 0; + margin-left: auto; + } +} diff --git a/public/css/bulma-tooltip.min.css b/public/css/bulma-tooltip.min.css new file mode 100644 index 0000000..421c678 --- /dev/null +++ b/public/css/bulma-tooltip.min.css @@ -0,0 +1 @@ +@-webkit-keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}[data-tooltip]:not(.is-disabled),[data-tooltip]:not(.is-loading),[data-tooltip]:not([disabled]){cursor:pointer;overflow:visible;position:relative}[data-tooltip]:not(.is-disabled)::after,[data-tooltip]:not(.is-disabled)::before,[data-tooltip]:not(.is-loading)::after,[data-tooltip]:not(.is-loading)::before,[data-tooltip]:not([disabled])::after,[data-tooltip]:not([disabled])::before{box-sizing:border-box;color:#fff;display:inline-block;font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:.75rem;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;opacity:0;overflow:hidden;pointer-events:none;position:absolute;visibility:hidden;z-index:1020}[data-tooltip]:not(.is-disabled)::after,[data-tooltip]:not(.is-loading)::after,[data-tooltip]:not([disabled])::after{content:'';border-style:solid;border-width:6px;border-color:rgba(74,74,74,.9) transparent transparent transparent;margin-bottom:-5px}[data-tooltip]:not(.is-disabled)::after,[data-tooltip]:not(.is-loading)::after,[data-tooltip]:not([disabled])::after{top:0;right:auto;bottom:auto;left:50%;margin-top:-5px;margin-right:auto;margin-bottom:auto;margin-left:-5px;border-color:rgba(74,74,74,.9) transparent transparent transparent}[data-tooltip]:not(.is-disabled)::before,[data-tooltip]:not(.is-loading)::before,[data-tooltip]:not([disabled])::before{background:rgba(74,74,74,.9);border-radius:2px;content:attr(data-tooltip);padding:.5rem 1rem;text-overflow:ellipsis;white-space:pre}[data-tooltip]:not(.is-disabled)::before,[data-tooltip]:not(.is-loading)::before,[data-tooltip]:not([disabled])::before{top:0;right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom::after,[data-tooltip]:not(.is-loading).has-tooltip-bottom::after,[data-tooltip]:not([disabled]).has-tooltip-bottom::after{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-right:auto;margin-bottom:-5px;margin-left:-5px;border-color:transparent transparent rgba(74,74,74,.9) transparent}[data-tooltip]:not(.is-disabled).has-tooltip-bottom::before,[data-tooltip]:not(.is-loading).has-tooltip-bottom::before,[data-tooltip]:not([disabled]).has-tooltip-bottom::before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;-webkit-transform:translate(-50%,100%);transform:translate(-50%,100%)}[data-tooltip]:not(.is-disabled).has-tooltip-left::after,[data-tooltip]:not(.is-loading).has-tooltip-left::after,[data-tooltip]:not([disabled]).has-tooltip-left::after{top:auto;right:auto;bottom:50%;left:0;margin-top:auto;margin-right:auto;margin-bottom:-6px;margin-left:-11px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left::before,[data-tooltip]:not(.is-loading).has-tooltip-left::before,[data-tooltip]:not([disabled]).has-tooltip-left::before{top:auto;right:auto;bottom:50%;left:-11px;-webkit-transform:translate(-100%,50%);transform:translate(-100%,50%)}[data-tooltip]:not(.is-disabled).has-tooltip-right::after,[data-tooltip]:not(.is-loading).has-tooltip-right::after,[data-tooltip]:not([disabled]).has-tooltip-right::after{top:auto;right:0;bottom:50%;left:auto;margin-top:auto;margin-right:-11px;margin-bottom:-6px;margin-left:auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right::before,[data-tooltip]:not(.is-loading).has-tooltip-right::before,[data-tooltip]:not([disabled]).has-tooltip-right::before{top:auto;right:-11px;bottom:50%;left:auto;margin-top:auto;-webkit-transform:translate(100%,50%);transform:translate(100%,50%)}[data-tooltip]:not(.is-disabled).has-tooltip-multiline::before,[data-tooltip]:not(.is-loading).has-tooltip-multiline::before,[data-tooltip]:not([disabled]).has-tooltip-multiline::before{height:auto;width:15rem;max-width:15rem;text-overflow:clip;white-space:normal;word-break:keep-all}[data-tooltip]:not(.is-disabled).has-tooltip-white.has-tooltip-bottom::after,[data-tooltip]:not(.is-loading).has-tooltip-white.has-tooltip-bottom::after,[data-tooltip]:not([disabled]).has-tooltip-white.has-tooltip-bottom::after{border-color:transparent transparent rgba(255,255,255,.9) transparent}[data-tooltip]:not(.is-disabled).has-tooltip-white.has-tooltip-left::after,[data-tooltip]:not(.is-loading).has-tooltip-white.has-tooltip-left::after,[data-tooltip]:not([disabled]).has-tooltip-white.has-tooltip-left::after{border-color:transparent transparent transparent rgba(255,255,255,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-white.has-tooltip-right::after,[data-tooltip]:not(.is-loading).has-tooltip-white.has-tooltip-right::after,[data-tooltip]:not([disabled]).has-tooltip-white.has-tooltip-right::after{border-color:transparent rgba(255,255,255,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-white:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-disabled).has-tooltip-white:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-disabled).has-tooltip-white:not(.has-tooltip-right)::after,[data-tooltip]:not(.is-loading).has-tooltip-white:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-loading).has-tooltip-white:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-loading).has-tooltip-white:not(.has-tooltip-right)::after,[data-tooltip]:not([disabled]).has-tooltip-white:not(.has-tooltip-bottom)::after,[data-tooltip]:not([disabled]).has-tooltip-white:not(.has-tooltip-left)::after,[data-tooltip]:not([disabled]).has-tooltip-white:not(.has-tooltip-right)::after{border-color:rgba(255,255,255,.9) transparent transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-white:before,[data-tooltip]:not(.is-loading).has-tooltip-white:before,[data-tooltip]:not([disabled]).has-tooltip-white:before{background-color:rgba(255,255,255,.9);color:#0a0a0a}[data-tooltip]:not(.is-disabled).has-tooltip-black.has-tooltip-bottom::after,[data-tooltip]:not(.is-loading).has-tooltip-black.has-tooltip-bottom::after,[data-tooltip]:not([disabled]).has-tooltip-black.has-tooltip-bottom::after{border-color:transparent transparent rgba(10,10,10,.9) transparent}[data-tooltip]:not(.is-disabled).has-tooltip-black.has-tooltip-left::after,[data-tooltip]:not(.is-loading).has-tooltip-black.has-tooltip-left::after,[data-tooltip]:not([disabled]).has-tooltip-black.has-tooltip-left::after{border-color:transparent transparent transparent rgba(10,10,10,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-black.has-tooltip-right::after,[data-tooltip]:not(.is-loading).has-tooltip-black.has-tooltip-right::after,[data-tooltip]:not([disabled]).has-tooltip-black.has-tooltip-right::after{border-color:transparent rgba(10,10,10,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-black:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-disabled).has-tooltip-black:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-disabled).has-tooltip-black:not(.has-tooltip-right)::after,[data-tooltip]:not(.is-loading).has-tooltip-black:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-loading).has-tooltip-black:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-loading).has-tooltip-black:not(.has-tooltip-right)::after,[data-tooltip]:not([disabled]).has-tooltip-black:not(.has-tooltip-bottom)::after,[data-tooltip]:not([disabled]).has-tooltip-black:not(.has-tooltip-left)::after,[data-tooltip]:not([disabled]).has-tooltip-black:not(.has-tooltip-right)::after{border-color:rgba(10,10,10,.9) transparent transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-black:before,[data-tooltip]:not(.is-loading).has-tooltip-black:before,[data-tooltip]:not([disabled]).has-tooltip-black:before{background-color:rgba(10,10,10,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-light.has-tooltip-bottom::after,[data-tooltip]:not(.is-loading).has-tooltip-light.has-tooltip-bottom::after,[data-tooltip]:not([disabled]).has-tooltip-light.has-tooltip-bottom::after{border-color:transparent transparent rgba(245,245,245,.9) transparent}[data-tooltip]:not(.is-disabled).has-tooltip-light.has-tooltip-left::after,[data-tooltip]:not(.is-loading).has-tooltip-light.has-tooltip-left::after,[data-tooltip]:not([disabled]).has-tooltip-light.has-tooltip-left::after{border-color:transparent transparent transparent rgba(245,245,245,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-light.has-tooltip-right::after,[data-tooltip]:not(.is-loading).has-tooltip-light.has-tooltip-right::after,[data-tooltip]:not([disabled]).has-tooltip-light.has-tooltip-right::after{border-color:transparent rgba(245,245,245,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-light:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-disabled).has-tooltip-light:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-disabled).has-tooltip-light:not(.has-tooltip-right)::after,[data-tooltip]:not(.is-loading).has-tooltip-light:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-loading).has-tooltip-light:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-loading).has-tooltip-light:not(.has-tooltip-right)::after,[data-tooltip]:not([disabled]).has-tooltip-light:not(.has-tooltip-bottom)::after,[data-tooltip]:not([disabled]).has-tooltip-light:not(.has-tooltip-left)::after,[data-tooltip]:not([disabled]).has-tooltip-light:not(.has-tooltip-right)::after{border-color:rgba(245,245,245,.9) transparent transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-light:before,[data-tooltip]:not(.is-loading).has-tooltip-light:before,[data-tooltip]:not([disabled]).has-tooltip-light:before{background-color:rgba(245,245,245,.9);color:#363636}[data-tooltip]:not(.is-disabled).has-tooltip-dark.has-tooltip-bottom::after,[data-tooltip]:not(.is-loading).has-tooltip-dark.has-tooltip-bottom::after,[data-tooltip]:not([disabled]).has-tooltip-dark.has-tooltip-bottom::after{border-color:transparent transparent rgba(54,54,54,.9) transparent}[data-tooltip]:not(.is-disabled).has-tooltip-dark.has-tooltip-left::after,[data-tooltip]:not(.is-loading).has-tooltip-dark.has-tooltip-left::after,[data-tooltip]:not([disabled]).has-tooltip-dark.has-tooltip-left::after{border-color:transparent transparent transparent rgba(54,54,54,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-dark.has-tooltip-right::after,[data-tooltip]:not(.is-loading).has-tooltip-dark.has-tooltip-right::after,[data-tooltip]:not([disabled]).has-tooltip-dark.has-tooltip-right::after{border-color:transparent rgba(54,54,54,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-dark:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-disabled).has-tooltip-dark:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-disabled).has-tooltip-dark:not(.has-tooltip-right)::after,[data-tooltip]:not(.is-loading).has-tooltip-dark:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-loading).has-tooltip-dark:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-loading).has-tooltip-dark:not(.has-tooltip-right)::after,[data-tooltip]:not([disabled]).has-tooltip-dark:not(.has-tooltip-bottom)::after,[data-tooltip]:not([disabled]).has-tooltip-dark:not(.has-tooltip-left)::after,[data-tooltip]:not([disabled]).has-tooltip-dark:not(.has-tooltip-right)::after{border-color:rgba(54,54,54,.9) transparent transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-dark:before,[data-tooltip]:not(.is-loading).has-tooltip-dark:before,[data-tooltip]:not([disabled]).has-tooltip-dark:before{background-color:rgba(54,54,54,.9);color:#f5f5f5}[data-tooltip]:not(.is-disabled).has-tooltip-primary.has-tooltip-bottom::after,[data-tooltip]:not(.is-loading).has-tooltip-primary.has-tooltip-bottom::after,[data-tooltip]:not([disabled]).has-tooltip-primary.has-tooltip-bottom::after{border-color:transparent transparent rgba(0,209,178,.9) transparent}[data-tooltip]:not(.is-disabled).has-tooltip-primary.has-tooltip-left::after,[data-tooltip]:not(.is-loading).has-tooltip-primary.has-tooltip-left::after,[data-tooltip]:not([disabled]).has-tooltip-primary.has-tooltip-left::after{border-color:transparent transparent transparent rgba(0,209,178,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-primary.has-tooltip-right::after,[data-tooltip]:not(.is-loading).has-tooltip-primary.has-tooltip-right::after,[data-tooltip]:not([disabled]).has-tooltip-primary.has-tooltip-right::after{border-color:transparent rgba(0,209,178,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-primary:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-disabled).has-tooltip-primary:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-disabled).has-tooltip-primary:not(.has-tooltip-right)::after,[data-tooltip]:not(.is-loading).has-tooltip-primary:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-loading).has-tooltip-primary:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-loading).has-tooltip-primary:not(.has-tooltip-right)::after,[data-tooltip]:not([disabled]).has-tooltip-primary:not(.has-tooltip-bottom)::after,[data-tooltip]:not([disabled]).has-tooltip-primary:not(.has-tooltip-left)::after,[data-tooltip]:not([disabled]).has-tooltip-primary:not(.has-tooltip-right)::after{border-color:rgba(0,209,178,.9) transparent transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-primary:before,[data-tooltip]:not(.is-loading).has-tooltip-primary:before,[data-tooltip]:not([disabled]).has-tooltip-primary:before{background-color:rgba(0,209,178,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-link.has-tooltip-bottom::after,[data-tooltip]:not(.is-loading).has-tooltip-link.has-tooltip-bottom::after,[data-tooltip]:not([disabled]).has-tooltip-link.has-tooltip-bottom::after{border-color:transparent transparent rgba(50,115,220,.9) transparent}[data-tooltip]:not(.is-disabled).has-tooltip-link.has-tooltip-left::after,[data-tooltip]:not(.is-loading).has-tooltip-link.has-tooltip-left::after,[data-tooltip]:not([disabled]).has-tooltip-link.has-tooltip-left::after{border-color:transparent transparent transparent rgba(50,115,220,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-link.has-tooltip-right::after,[data-tooltip]:not(.is-loading).has-tooltip-link.has-tooltip-right::after,[data-tooltip]:not([disabled]).has-tooltip-link.has-tooltip-right::after{border-color:transparent rgba(50,115,220,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-link:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-disabled).has-tooltip-link:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-disabled).has-tooltip-link:not(.has-tooltip-right)::after,[data-tooltip]:not(.is-loading).has-tooltip-link:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-loading).has-tooltip-link:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-loading).has-tooltip-link:not(.has-tooltip-right)::after,[data-tooltip]:not([disabled]).has-tooltip-link:not(.has-tooltip-bottom)::after,[data-tooltip]:not([disabled]).has-tooltip-link:not(.has-tooltip-left)::after,[data-tooltip]:not([disabled]).has-tooltip-link:not(.has-tooltip-right)::after{border-color:rgba(50,115,220,.9) transparent transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-link:before,[data-tooltip]:not(.is-loading).has-tooltip-link:before,[data-tooltip]:not([disabled]).has-tooltip-link:before{background-color:rgba(50,115,220,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-info.has-tooltip-bottom::after,[data-tooltip]:not(.is-loading).has-tooltip-info.has-tooltip-bottom::after,[data-tooltip]:not([disabled]).has-tooltip-info.has-tooltip-bottom::after{border-color:transparent transparent rgba(32,156,238,.9) transparent}[data-tooltip]:not(.is-disabled).has-tooltip-info.has-tooltip-left::after,[data-tooltip]:not(.is-loading).has-tooltip-info.has-tooltip-left::after,[data-tooltip]:not([disabled]).has-tooltip-info.has-tooltip-left::after{border-color:transparent transparent transparent rgba(32,156,238,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-info.has-tooltip-right::after,[data-tooltip]:not(.is-loading).has-tooltip-info.has-tooltip-right::after,[data-tooltip]:not([disabled]).has-tooltip-info.has-tooltip-right::after{border-color:transparent rgba(32,156,238,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-info:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-disabled).has-tooltip-info:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-disabled).has-tooltip-info:not(.has-tooltip-right)::after,[data-tooltip]:not(.is-loading).has-tooltip-info:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-loading).has-tooltip-info:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-loading).has-tooltip-info:not(.has-tooltip-right)::after,[data-tooltip]:not([disabled]).has-tooltip-info:not(.has-tooltip-bottom)::after,[data-tooltip]:not([disabled]).has-tooltip-info:not(.has-tooltip-left)::after,[data-tooltip]:not([disabled]).has-tooltip-info:not(.has-tooltip-right)::after{border-color:rgba(32,156,238,.9) transparent transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-info:before,[data-tooltip]:not(.is-loading).has-tooltip-info:before,[data-tooltip]:not([disabled]).has-tooltip-info:before{background-color:rgba(32,156,238,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-success.has-tooltip-bottom::after,[data-tooltip]:not(.is-loading).has-tooltip-success.has-tooltip-bottom::after,[data-tooltip]:not([disabled]).has-tooltip-success.has-tooltip-bottom::after{border-color:transparent transparent rgba(35,209,96,.9) transparent}[data-tooltip]:not(.is-disabled).has-tooltip-success.has-tooltip-left::after,[data-tooltip]:not(.is-loading).has-tooltip-success.has-tooltip-left::after,[data-tooltip]:not([disabled]).has-tooltip-success.has-tooltip-left::after{border-color:transparent transparent transparent rgba(35,209,96,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-success.has-tooltip-right::after,[data-tooltip]:not(.is-loading).has-tooltip-success.has-tooltip-right::after,[data-tooltip]:not([disabled]).has-tooltip-success.has-tooltip-right::after{border-color:transparent rgba(35,209,96,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-success:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-disabled).has-tooltip-success:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-disabled).has-tooltip-success:not(.has-tooltip-right)::after,[data-tooltip]:not(.is-loading).has-tooltip-success:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-loading).has-tooltip-success:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-loading).has-tooltip-success:not(.has-tooltip-right)::after,[data-tooltip]:not([disabled]).has-tooltip-success:not(.has-tooltip-bottom)::after,[data-tooltip]:not([disabled]).has-tooltip-success:not(.has-tooltip-left)::after,[data-tooltip]:not([disabled]).has-tooltip-success:not(.has-tooltip-right)::after{border-color:rgba(35,209,96,.9) transparent transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-success:before,[data-tooltip]:not(.is-loading).has-tooltip-success:before,[data-tooltip]:not([disabled]).has-tooltip-success:before{background-color:rgba(35,209,96,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-warning.has-tooltip-bottom::after,[data-tooltip]:not(.is-loading).has-tooltip-warning.has-tooltip-bottom::after,[data-tooltip]:not([disabled]).has-tooltip-warning.has-tooltip-bottom::after{border-color:transparent transparent rgba(255,221,87,.9) transparent}[data-tooltip]:not(.is-disabled).has-tooltip-warning.has-tooltip-left::after,[data-tooltip]:not(.is-loading).has-tooltip-warning.has-tooltip-left::after,[data-tooltip]:not([disabled]).has-tooltip-warning.has-tooltip-left::after{border-color:transparent transparent transparent rgba(255,221,87,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-warning.has-tooltip-right::after,[data-tooltip]:not(.is-loading).has-tooltip-warning.has-tooltip-right::after,[data-tooltip]:not([disabled]).has-tooltip-warning.has-tooltip-right::after{border-color:transparent rgba(255,221,87,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-warning:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-disabled).has-tooltip-warning:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-disabled).has-tooltip-warning:not(.has-tooltip-right)::after,[data-tooltip]:not(.is-loading).has-tooltip-warning:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-loading).has-tooltip-warning:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-loading).has-tooltip-warning:not(.has-tooltip-right)::after,[data-tooltip]:not([disabled]).has-tooltip-warning:not(.has-tooltip-bottom)::after,[data-tooltip]:not([disabled]).has-tooltip-warning:not(.has-tooltip-left)::after,[data-tooltip]:not([disabled]).has-tooltip-warning:not(.has-tooltip-right)::after{border-color:rgba(255,221,87,.9) transparent transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-warning:before,[data-tooltip]:not(.is-loading).has-tooltip-warning:before,[data-tooltip]:not([disabled]).has-tooltip-warning:before{background-color:rgba(255,221,87,.9);color:rgba(0,0,0,.7)}[data-tooltip]:not(.is-disabled).has-tooltip-danger.has-tooltip-bottom::after,[data-tooltip]:not(.is-loading).has-tooltip-danger.has-tooltip-bottom::after,[data-tooltip]:not([disabled]).has-tooltip-danger.has-tooltip-bottom::after{border-color:transparent transparent rgba(255,56,96,.9) transparent}[data-tooltip]:not(.is-disabled).has-tooltip-danger.has-tooltip-left::after,[data-tooltip]:not(.is-loading).has-tooltip-danger.has-tooltip-left::after,[data-tooltip]:not([disabled]).has-tooltip-danger.has-tooltip-left::after{border-color:transparent transparent transparent rgba(255,56,96,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-danger.has-tooltip-right::after,[data-tooltip]:not(.is-loading).has-tooltip-danger.has-tooltip-right::after,[data-tooltip]:not([disabled]).has-tooltip-danger.has-tooltip-right::after{border-color:transparent rgba(255,56,96,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-danger:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-disabled).has-tooltip-danger:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-disabled).has-tooltip-danger:not(.has-tooltip-right)::after,[data-tooltip]:not(.is-loading).has-tooltip-danger:not(.has-tooltip-bottom)::after,[data-tooltip]:not(.is-loading).has-tooltip-danger:not(.has-tooltip-left)::after,[data-tooltip]:not(.is-loading).has-tooltip-danger:not(.has-tooltip-right)::after,[data-tooltip]:not([disabled]).has-tooltip-danger:not(.has-tooltip-bottom)::after,[data-tooltip]:not([disabled]).has-tooltip-danger:not(.has-tooltip-left)::after,[data-tooltip]:not([disabled]).has-tooltip-danger:not(.has-tooltip-right)::after{border-color:rgba(255,56,96,.9) transparent transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-danger:before,[data-tooltip]:not(.is-loading).has-tooltip-danger:before,[data-tooltip]:not([disabled]).has-tooltip-danger:before{background-color:rgba(255,56,96,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-active::after,[data-tooltip]:not(.is-disabled).has-tooltip-active::before,[data-tooltip]:not(.is-disabled):focus::after,[data-tooltip]:not(.is-disabled):focus::before,[data-tooltip]:not(.is-disabled):hover::after,[data-tooltip]:not(.is-disabled):hover::before,[data-tooltip]:not(.is-loading).has-tooltip-active::after,[data-tooltip]:not(.is-loading).has-tooltip-active::before,[data-tooltip]:not(.is-loading):focus::after,[data-tooltip]:not(.is-loading):focus::before,[data-tooltip]:not(.is-loading):hover::after,[data-tooltip]:not(.is-loading):hover::before,[data-tooltip]:not([disabled]).has-tooltip-active::after,[data-tooltip]:not([disabled]).has-tooltip-active::before,[data-tooltip]:not([disabled]):focus::after,[data-tooltip]:not([disabled]):focus::before,[data-tooltip]:not([disabled]):hover::after,[data-tooltip]:not([disabled]):hover::before{opacity:1;visibility:visible}[data-tooltip]:not(.is-disabled).has-tooltip-fade::after,[data-tooltip]:not(.is-disabled).has-tooltip-fade::before,[data-tooltip]:not(.is-loading).has-tooltip-fade::after,[data-tooltip]:not(.is-loading).has-tooltip-fade::before,[data-tooltip]:not([disabled]).has-tooltip-fade::after,[data-tooltip]:not([disabled]).has-tooltip-fade::before{transition:opacity .3s linear,visibility .3s linear}@media screen and (max-width:768px){.has-tooltip-top-mobile::after{top:0;right:auto;bottom:auto;left:50%;margin-top:-5px;margin-right:auto;margin-bottom:auto;margin-left:-5px;border-color:rgba(74,74,74,.9) transparent transparent transparent}.has-tooltip-top-mobile::before{top:0;right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}}@media screen and (min-width:769px),print{.has-tooltip-top-tablet::after{top:0;right:auto;bottom:auto;left:50%;margin-top:-5px;margin-right:auto;margin-bottom:auto;margin-left:-5px;border-color:rgba(74,74,74,.9) transparent transparent transparent}.has-tooltip-top-tablet::before{top:0;right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}}@media screen and (min-width:769px) and (max-width:1087px){.has-tooltip-top-tablet-only::after{top:0;right:auto;bottom:auto;left:50%;margin-top:-5px;margin-right:auto;margin-bottom:auto;margin-left:-5px;border-color:rgba(74,74,74,.9) transparent transparent transparent}.has-tooltip-top-tablet-only::before{top:0;right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}}@media screen and (max-width:1087px){.has-tooltip-top-touch::after{top:0;right:auto;bottom:auto;left:50%;margin-top:-5px;margin-right:auto;margin-bottom:auto;margin-left:-5px;border-color:rgba(74,74,74,.9) transparent transparent transparent}.has-tooltip-top-touch::before{top:0;right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}}@media screen and (min-width:1088px){.has-tooltip-top-desktop::after{top:0;right:auto;bottom:auto;left:50%;margin-top:-5px;margin-right:auto;margin-bottom:auto;margin-left:-5px;border-color:rgba(74,74,74,.9) transparent transparent transparent}.has-tooltip-top-desktop::before{top:0;right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}}@media screen and (min-width:1088px) and (max-width:1279px){.has-tooltip-top-desktop-only::after{top:0;right:auto;bottom:auto;left:50%;margin-top:-5px;margin-right:auto;margin-bottom:auto;margin-left:-5px;border-color:rgba(74,74,74,.9) transparent transparent transparent}.has-tooltip-top-desktop-only::before{top:0;right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}}@media screen and (max-width:1279px){.has-tooltip-top-until-widescreen::after{top:0;right:auto;bottom:auto;left:50%;margin-top:-5px;margin-right:auto;margin-bottom:auto;margin-left:-5px;border-color:rgba(74,74,74,.9) transparent transparent transparent}.has-tooltip-top-until-widescreen::before{top:0;right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}}@media screen and (min-width:1280px){.has-tooltip-top-widescreen::after{top:0;right:auto;bottom:auto;left:50%;margin-top:-5px;margin-right:auto;margin-bottom:auto;margin-left:-5px;border-color:rgba(74,74,74,.9) transparent transparent transparent}.has-tooltip-top-widescreen::before{top:0;right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}}@media screen and (min-width:1280px) and (max-width:1471px){.has-tooltip-top-widescreen-only::after{top:0;right:auto;bottom:auto;left:50%;margin-top:-5px;margin-right:auto;margin-bottom:auto;margin-left:-5px;border-color:rgba(74,74,74,.9) transparent transparent transparent}.has-tooltip-top-widescreen-only::before{top:0;right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}}@media screen and (max-width:1471px){.has-tooltip-top-until-fullhd::after{top:0;right:auto;bottom:auto;left:50%;margin-top:-5px;margin-right:auto;margin-bottom:auto;margin-left:-5px;border-color:rgba(74,74,74,.9) transparent transparent transparent}.has-tooltip-top-until-fullhd::before{top:0;right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}}@media screen and (min-width:1472px){.has-tooltip-top-fullhd::after{top:0;right:auto;bottom:auto;left:50%;margin-top:-5px;margin-right:auto;margin-bottom:auto;margin-left:-5px;border-color:rgba(74,74,74,.9) transparent transparent transparent}.has-tooltip-top-fullhd::before{top:0;right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}}@media screen and (max-width:768px){.has-tooltip-right-mobile::after{top:auto;right:0;bottom:50%;left:auto;margin-top:auto;margin-right:-11px;margin-bottom:-6px;margin-left:auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}.has-tooltip-right-mobile::before{top:auto;right:-11px;bottom:50%;left:auto;margin-top:auto;-webkit-transform:translate(100%,50%);transform:translate(100%,50%)}}@media screen and (min-width:769px),print{.has-tooltip-right-tablet::after{top:auto;right:0;bottom:50%;left:auto;margin-top:auto;margin-right:-11px;margin-bottom:-6px;margin-left:auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}.has-tooltip-right-tablet::before{top:auto;right:-11px;bottom:50%;left:auto;margin-top:auto;-webkit-transform:translate(100%,50%);transform:translate(100%,50%)}}@media screen and (min-width:769px) and (max-width:1087px){.has-tooltip-right-tablet-only::after{top:auto;right:0;bottom:50%;left:auto;margin-top:auto;margin-right:-11px;margin-bottom:-6px;margin-left:auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}.has-tooltip-right-tablet-only::before{top:auto;right:-11px;bottom:50%;left:auto;margin-top:auto;-webkit-transform:translate(100%,50%);transform:translate(100%,50%)}}@media screen and (max-width:1087px){.has-tooltip-right-touch::after{top:auto;right:0;bottom:50%;left:auto;margin-top:auto;margin-right:-11px;margin-bottom:-6px;margin-left:auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}.has-tooltip-right-touch::before{top:auto;right:-11px;bottom:50%;left:auto;margin-top:auto;-webkit-transform:translate(100%,50%);transform:translate(100%,50%)}}@media screen and (min-width:1088px){.has-tooltip-right-desktop::after{top:auto;right:0;bottom:50%;left:auto;margin-top:auto;margin-right:-11px;margin-bottom:-6px;margin-left:auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}.has-tooltip-right-desktop::before{top:auto;right:-11px;bottom:50%;left:auto;margin-top:auto;-webkit-transform:translate(100%,50%);transform:translate(100%,50%)}}@media screen and (min-width:1088px) and (max-width:1279px){.has-tooltip-right-desktop-only::after{top:auto;right:0;bottom:50%;left:auto;margin-top:auto;margin-right:-11px;margin-bottom:-6px;margin-left:auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}.has-tooltip-right-desktop-only::before{top:auto;right:-11px;bottom:50%;left:auto;margin-top:auto;-webkit-transform:translate(100%,50%);transform:translate(100%,50%)}}@media screen and (max-width:1279px){.has-tooltip-right-until-widescreen::after{top:auto;right:0;bottom:50%;left:auto;margin-top:auto;margin-right:-11px;margin-bottom:-6px;margin-left:auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}.has-tooltip-right-until-widescreen::before{top:auto;right:-11px;bottom:50%;left:auto;margin-top:auto;-webkit-transform:translate(100%,50%);transform:translate(100%,50%)}}@media screen and (min-width:1280px){.has-tooltip-right-widescreen::after{top:auto;right:0;bottom:50%;left:auto;margin-top:auto;margin-right:-11px;margin-bottom:-6px;margin-left:auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}.has-tooltip-right-widescreen::before{top:auto;right:-11px;bottom:50%;left:auto;margin-top:auto;-webkit-transform:translate(100%,50%);transform:translate(100%,50%)}}@media screen and (min-width:1280px) and (max-width:1471px){.has-tooltip-right-widescreen-only::after{top:auto;right:0;bottom:50%;left:auto;margin-top:auto;margin-right:-11px;margin-bottom:-6px;margin-left:auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}.has-tooltip-right-widescreen-only::before{top:auto;right:-11px;bottom:50%;left:auto;margin-top:auto;-webkit-transform:translate(100%,50%);transform:translate(100%,50%)}}@media screen and (max-width:1471px){.has-tooltip-right-until-fullhd::after{top:auto;right:0;bottom:50%;left:auto;margin-top:auto;margin-right:-11px;margin-bottom:-6px;margin-left:auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}.has-tooltip-right-until-fullhd::before{top:auto;right:-11px;bottom:50%;left:auto;margin-top:auto;-webkit-transform:translate(100%,50%);transform:translate(100%,50%)}}@media screen and (min-width:1472px){.has-tooltip-right-fullhd::after{top:auto;right:0;bottom:50%;left:auto;margin-top:auto;margin-right:-11px;margin-bottom:-6px;margin-left:auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}.has-tooltip-right-fullhd::before{top:auto;right:-11px;bottom:50%;left:auto;margin-top:auto;-webkit-transform:translate(100%,50%);transform:translate(100%,50%)}}@media screen and (max-width:768px){.has-tooltip-bottom-mobile::after{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-right:auto;margin-bottom:-5px;margin-left:-5px;border-color:transparent transparent rgba(74,74,74,.9) transparent}.has-tooltip-bottom-mobile::before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;-webkit-transform:translate(-50%,100%);transform:translate(-50%,100%)}}@media screen and (min-width:769px),print{.has-tooltip-bottom-tablet::after{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-right:auto;margin-bottom:-5px;margin-left:-5px;border-color:transparent transparent rgba(74,74,74,.9) transparent}.has-tooltip-bottom-tablet::before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;-webkit-transform:translate(-50%,100%);transform:translate(-50%,100%)}}@media screen and (min-width:769px) and (max-width:1087px){.has-tooltip-bottom-tablet-only::after{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-right:auto;margin-bottom:-5px;margin-left:-5px;border-color:transparent transparent rgba(74,74,74,.9) transparent}.has-tooltip-bottom-tablet-only::before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;-webkit-transform:translate(-50%,100%);transform:translate(-50%,100%)}}@media screen and (max-width:1087px){.has-tooltip-bottom-touch::after{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-right:auto;margin-bottom:-5px;margin-left:-5px;border-color:transparent transparent rgba(74,74,74,.9) transparent}.has-tooltip-bottom-touch::before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;-webkit-transform:translate(-50%,100%);transform:translate(-50%,100%)}}@media screen and (min-width:1088px){.has-tooltip-bottom-desktop::after{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-right:auto;margin-bottom:-5px;margin-left:-5px;border-color:transparent transparent rgba(74,74,74,.9) transparent}.has-tooltip-bottom-desktop::before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;-webkit-transform:translate(-50%,100%);transform:translate(-50%,100%)}}@media screen and (min-width:1088px) and (max-width:1279px){.has-tooltip-bottom-desktop-only::after{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-right:auto;margin-bottom:-5px;margin-left:-5px;border-color:transparent transparent rgba(74,74,74,.9) transparent}.has-tooltip-bottom-desktop-only::before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;-webkit-transform:translate(-50%,100%);transform:translate(-50%,100%)}}@media screen and (max-width:1279px){.has-tooltip-bottom-until-widescreen::after{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-right:auto;margin-bottom:-5px;margin-left:-5px;border-color:transparent transparent rgba(74,74,74,.9) transparent}.has-tooltip-bottom-until-widescreen::before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;-webkit-transform:translate(-50%,100%);transform:translate(-50%,100%)}}@media screen and (min-width:1280px){.has-tooltip-bottom-widescreen::after{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-right:auto;margin-bottom:-5px;margin-left:-5px;border-color:transparent transparent rgba(74,74,74,.9) transparent}.has-tooltip-bottom-widescreen::before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;-webkit-transform:translate(-50%,100%);transform:translate(-50%,100%)}}@media screen and (min-width:1280px) and (max-width:1471px){.has-tooltip-bottom-widescreen-only::after{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-right:auto;margin-bottom:-5px;margin-left:-5px;border-color:transparent transparent rgba(74,74,74,.9) transparent}.has-tooltip-bottom-widescreen-only::before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;-webkit-transform:translate(-50%,100%);transform:translate(-50%,100%)}}@media screen and (max-width:1471px){.has-tooltip-bottom-until-fullhd::after{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-right:auto;margin-bottom:-5px;margin-left:-5px;border-color:transparent transparent rgba(74,74,74,.9) transparent}.has-tooltip-bottom-until-fullhd::before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;-webkit-transform:translate(-50%,100%);transform:translate(-50%,100%)}}@media screen and (min-width:1472px){.has-tooltip-bottom-fullhd::after{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-right:auto;margin-bottom:-5px;margin-left:-5px;border-color:transparent transparent rgba(74,74,74,.9) transparent}.has-tooltip-bottom-fullhd::before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;-webkit-transform:translate(-50%,100%);transform:translate(-50%,100%)}}@media screen and (max-width:768px){.has-tooltip-left-mobile::after{top:auto;right:auto;bottom:50%;left:0;margin-top:auto;margin-right:auto;margin-bottom:-6px;margin-left:-11px;border-color:transparent transparent transparent rgba(74,74,74,.9)}.has-tooltip-left-mobile::before{top:auto;right:auto;bottom:50%;left:-11px;-webkit-transform:translate(-100%,50%);transform:translate(-100%,50%)}}@media screen and (min-width:769px),print{.has-tooltip-left-tablet::after{top:auto;right:auto;bottom:50%;left:0;margin-top:auto;margin-right:auto;margin-bottom:-6px;margin-left:-11px;border-color:transparent transparent transparent rgba(74,74,74,.9)}.has-tooltip-left-tablet::before{top:auto;right:auto;bottom:50%;left:-11px;-webkit-transform:translate(-100%,50%);transform:translate(-100%,50%)}}@media screen and (min-width:769px) and (max-width:1087px){.has-tooltip-left-tablet-only::after{top:auto;right:auto;bottom:50%;left:0;margin-top:auto;margin-right:auto;margin-bottom:-6px;margin-left:-11px;border-color:transparent transparent transparent rgba(74,74,74,.9)}.has-tooltip-left-tablet-only::before{top:auto;right:auto;bottom:50%;left:-11px;-webkit-transform:translate(-100%,50%);transform:translate(-100%,50%)}}@media screen and (max-width:1087px){.has-tooltip-left-touch::after{top:auto;right:auto;bottom:50%;left:0;margin-top:auto;margin-right:auto;margin-bottom:-6px;margin-left:-11px;border-color:transparent transparent transparent rgba(74,74,74,.9)}.has-tooltip-left-touch::before{top:auto;right:auto;bottom:50%;left:-11px;-webkit-transform:translate(-100%,50%);transform:translate(-100%,50%)}}@media screen and (min-width:1088px){.has-tooltip-left-desktop::after{top:auto;right:auto;bottom:50%;left:0;margin-top:auto;margin-right:auto;margin-bottom:-6px;margin-left:-11px;border-color:transparent transparent transparent rgba(74,74,74,.9)}.has-tooltip-left-desktop::before{top:auto;right:auto;bottom:50%;left:-11px;-webkit-transform:translate(-100%,50%);transform:translate(-100%,50%)}}@media screen and (min-width:1088px) and (max-width:1279px){.has-tooltip-left-desktop-only::after{top:auto;right:auto;bottom:50%;left:0;margin-top:auto;margin-right:auto;margin-bottom:-6px;margin-left:-11px;border-color:transparent transparent transparent rgba(74,74,74,.9)}.has-tooltip-left-desktop-only::before{top:auto;right:auto;bottom:50%;left:-11px;-webkit-transform:translate(-100%,50%);transform:translate(-100%,50%)}}@media screen and (max-width:1279px){.has-tooltip-left-until-widescreen::after{top:auto;right:auto;bottom:50%;left:0;margin-top:auto;margin-right:auto;margin-bottom:-6px;margin-left:-11px;border-color:transparent transparent transparent rgba(74,74,74,.9)}.has-tooltip-left-until-widescreen::before{top:auto;right:auto;bottom:50%;left:-11px;-webkit-transform:translate(-100%,50%);transform:translate(-100%,50%)}}@media screen and (min-width:1280px){.has-tooltip-left-widescreen::after{top:auto;right:auto;bottom:50%;left:0;margin-top:auto;margin-right:auto;margin-bottom:-6px;margin-left:-11px;border-color:transparent transparent transparent rgba(74,74,74,.9)}.has-tooltip-left-widescreen::before{top:auto;right:auto;bottom:50%;left:-11px;-webkit-transform:translate(-100%,50%);transform:translate(-100%,50%)}}@media screen and (min-width:1280px) and (max-width:1471px){.has-tooltip-left-widescreen-only::after{top:auto;right:auto;bottom:50%;left:0;margin-top:auto;margin-right:auto;margin-bottom:-6px;margin-left:-11px;border-color:transparent transparent transparent rgba(74,74,74,.9)}.has-tooltip-left-widescreen-only::before{top:auto;right:auto;bottom:50%;left:-11px;-webkit-transform:translate(-100%,50%);transform:translate(-100%,50%)}}@media screen and (max-width:1471px){.has-tooltip-left-until-fullhd::after{top:auto;right:auto;bottom:50%;left:0;margin-top:auto;margin-right:auto;margin-bottom:-6px;margin-left:-11px;border-color:transparent transparent transparent rgba(74,74,74,.9)}.has-tooltip-left-until-fullhd::before{top:auto;right:auto;bottom:50%;left:-11px;-webkit-transform:translate(-100%,50%);transform:translate(-100%,50%)}}@media screen and (min-width:1472px){.has-tooltip-left-fullhd::after{top:auto;right:auto;bottom:50%;left:0;margin-top:auto;margin-right:auto;margin-bottom:-6px;margin-left:-11px;border-color:transparent transparent transparent rgba(74,74,74,.9)}.has-tooltip-left-fullhd::before{top:auto;right:auto;bottom:50%;left:-11px;-webkit-transform:translate(-100%,50%);transform:translate(-100%,50%)}} diff --git a/public/js/app.js b/public/js/app.js index 85157db..005dcde 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -94,6 +94,15 @@ Array.from(fieldsetsWithToggle).forEach(function(fieldset) { }); +// Init upload feedback text +Array.from(document.querySelectorAll('.form-input.file-input')).forEach(function(fileUpload) { + fileUpload.addEventListener('input', function (e) { + infoContainerId = e.target.getAttribute('data-info-container-id'); + infoContainer = document.getElementById(infoContainerId); + infoContainer.classList.remove('hidden'); + }) +}); + // Helper function to enable or disable a fieldset function toggleFieldset(fieldset, formInput) { var toggleValue = fieldset.getAttribute('data-toggle-value'); diff --git a/src/Actions/DownloadAction.php b/src/Actions/DownloadAction.php index 2d5d3d1..1d0e14b 100644 --- a/src/Actions/DownloadAction.php +++ b/src/Actions/DownloadAction.php @@ -2,6 +2,7 @@ namespace CosmoCode\Formserver\Actions; +use CosmoCode\Formserver\Helper\FileHelper; use DI\NotFoundException; use Mimey\MimeTypes; use Psr\Http\Message\ResponseInterface as Response; @@ -32,27 +33,22 @@ protected function action(): Response ); $filePath = self::DATA_DIRECTORY . $directory . '/' . $file; - if (! file_exists($filePath)) { - throw new NotFoundException(); - } + if (file_exists($filePath)) { + $mimes = new MimeTypes(); + $extension = FileHelper::getFileExtension($filePath); + $mimeType = $mimes->getMimeType($extension); - $mimes = new MimeTypes(); - $extension = strtolower( - pathinfo( - $filePath, - PATHINFO_EXTENSION - ) - ); - $mimeType = $mimes->getMimeType($extension); + $file = fopen($filePath, 'rb'); + $fileStream = new Stream($file); + $fileSize = filesize($filePath); - $file = fopen($filePath, 'rb'); - $fileStream = new Stream($file); - $fileSize = filesize($filePath); + return $this->response + ->withHeader('Content-Type', $mimeType) + ->withHeader('Content-Length', $fileSize) + ->withBody($fileStream); + } - return $this->response - ->withHeader('Content-Type', $mimeType) - ->withHeader('Content-Length', $fileSize) - ->withBody($fileStream); + return $this->response->withStatus(404); } /** diff --git a/src/Actions/FormAction.php b/src/Actions/FormAction.php index 86db24b..b2b3e5a 100644 --- a/src/Actions/FormAction.php +++ b/src/Actions/FormAction.php @@ -3,10 +3,13 @@ namespace CosmoCode\Formserver\Actions; +use CosmoCode\Formserver\Exceptions\FormException; +use CosmoCode\Formserver\Exceptions\LanguageException; use CosmoCode\Formserver\Exceptions\MailException; use CosmoCode\Formserver\FormGenerator\Form; use CosmoCode\Formserver\FormGenerator\FormRenderer; use CosmoCode\Formserver\FormGenerator\FormValidator; +use CosmoCode\Formserver\Service\FileExporter; use CosmoCode\Formserver\Service\LangManager; use CosmoCode\Formserver\Service\Mailer; use Psr\Http\Message\ResponseInterface as Response; @@ -24,14 +27,21 @@ class FormAction extends AbstractAction */ protected $mailer; + /** + * @var FileExporter + */ + protected $fileExporter; + /** * Constructor to inject dependencies * * @param Mailer $mailer + * @param FileExporter $fileExporter */ - public function __construct(Mailer $mailer) + public function __construct(Mailer $mailer, FileExporter $fileExporter) { $this->mailer = $mailer; + $this->fileExporter = $fileExporter; } /** @@ -44,6 +54,8 @@ protected function action(): Response try { $id = $this->resolveArg('id'); $form = new Form($id); + + LangManager::init($form->getMeta('language')); $formRenderer = new FormRenderer($form); $formValidator = new FormValidator($form); @@ -58,6 +70,7 @@ protected function action(): Response if ($form->isValid() && $form->getMode() === Form::MODE_SEND) { $this->mailer->sendForm($form); + $this->handleFileExport($form); } } elseif ($this->request->getMethod() === 'GET') { $form->restore(); @@ -73,4 +86,22 @@ protected function action(): Response return $this->response; } + + /** + * Helper function to copy file to another location when form gets sent + * + * @param Form $form + * @return void + * @throws FormException + */ + protected function handleFileExport(Form $form) + { + $file = $form->getMeta('export') ?? ''; + if ($file !== '') { + $formId = $form->getId(); + $filePath = $form->getFormDirectory() . $file; + + $this->fileExporter->moveFile($filePath, $formId); + } + } } diff --git a/src/Exceptions/LanguageException.php b/src/Exceptions/LanguageException.php new file mode 100644 index 0000000..5e52c05 --- /dev/null +++ b/src/Exceptions/LanguageException.php @@ -0,0 +1,13 @@ +getFormDirectory() . 'values.yaml')) { - return; - } - - $values = YamlHelper::parseYaml($this->getFormDirectory() . 'values.yaml'); + // Form was saved before. Restore data + if (is_file($this->getFormDirectory() . 'values.yaml')) { + $values = YamlHelper::parseYaml( + $this->getFormDirectory() . 'values.yaml' + ); - foreach ($this->formElements as $formElement) { - $this->restoreValue($values, $formElement); + foreach ($this->formElements as $formElement) { + $this->restoreValue($values, $formElement); + } + } else { + // Form was never saved before. Set default values + foreach ($this->formElements as $formElement) { + $this->setDefaultValues($formElement); + } } } @@ -281,11 +288,8 @@ protected function moveUploadedFile( UploadedFile $uploadedFile, UploadFormElement $formElement ) { - $extension = strtolower( - pathinfo( - $uploadedFile->getClientFilename(), - PATHINFO_EXTENSION - ) + $extension = FileHelper::getFileExtension( + $uploadedFile->getClientFilename() ); $baseName = $formElement->getId(); @@ -390,6 +394,23 @@ protected function restoreValue(array $values, AbstractFormElement $formElement) } } + /** + * Helper function to restore a specific value to a form element + * + * @param AbstractFormElement $formElement + * @return void + */ + protected function setDefaultValues(AbstractFormElement $formElement) + { + if ($formElement instanceof FieldsetFormElement) { + foreach ($formElement->getChildren() as $fieldsetChild) { + $this->setDefaultValues($fieldsetChild); + } + } elseif ($formElement instanceof ChecklistFormElement || $formElement instanceof DropdownFormElement) { + $formElement->setDefaultValue(); + } + } + /** * Helper function to restore a specific value to a form element * diff --git a/src/FormGenerator/FormElementFactory.php b/src/FormGenerator/FormElementFactory.php index 79e2069..af17953 100644 --- a/src/FormGenerator/FormElementFactory.php +++ b/src/FormGenerator/FormElementFactory.php @@ -13,11 +13,13 @@ use CosmoCode\Formserver\FormGenerator\FormElements\EmailFormElement; use CosmoCode\Formserver\FormGenerator\FormElements\FieldsetFormElement; use CosmoCode\Formserver\FormGenerator\FormElements\HiddenFormElement; +use CosmoCode\Formserver\FormGenerator\FormElements\HrFormElement; use CosmoCode\Formserver\FormGenerator\FormElements\ImageFormElement; use CosmoCode\Formserver\FormGenerator\FormElements\MarkDownFormElement; use CosmoCode\Formserver\FormGenerator\FormElements\NumberInputFormElement; use CosmoCode\Formserver\FormGenerator\FormElements\RadiosetFormElement; use CosmoCode\Formserver\FormGenerator\FormElements\SignatureFormElement; +use CosmoCode\Formserver\FormGenerator\FormElements\SpacerFormElement; use CosmoCode\Formserver\FormGenerator\FormElements\TextAreaFormElement; use CosmoCode\Formserver\FormGenerator\FormElements\TextInputFormElement; use CosmoCode\Formserver\FormGenerator\FormElements\TimeFormElement; @@ -55,6 +57,8 @@ public static function createFormElement( return new DownloadFormElement($id, $config, $parent); case 'image': return new ImageFormElement($id, $config, $parent); + case 'hr': + return new HrFormElement($id, $config, $parent); case 'upload': return new UploadFormElement($id, $config, $parent); case 'textinput': @@ -79,6 +83,8 @@ public static function createFormElement( return new DropdownFormElement($id, $config, $parent); case 'signature': return new SignatureFormElement($id, $config, $parent); + case 'spacer': + return new SpacerFormElement($id, $config, $parent); default: throw new FormException( "Could not build FormElement id:$id. Undefined type ($formType)" diff --git a/src/FormGenerator/FormElements/AbstractDynamicFormElement.php b/src/FormGenerator/FormElements/AbstractDynamicFormElement.php index ab86da4..dab1b49 100644 --- a/src/FormGenerator/FormElements/AbstractDynamicFormElement.php +++ b/src/FormGenerator/FormElements/AbstractDynamicFormElement.php @@ -141,8 +141,23 @@ public function getViewVariables() 'id_string' => $this->getFormElementIdStringified(), 'value' => $this->getValue(), 'errors' => $this->getErrors(), - 'is_required' => $this->isRequired() + 'is_required' => $this->isRequired(), + 'tooltip' => $this->parseTooltip() ] ); } + + /** + * Parses tooltip. + * Transform newlines (\n) to NCR representation. + * If no tooltip given return empty string. + * + * @return string + */ + protected function parseTooltip() + { + $tooltip = $this->getConfigValue('tooltip') ?? ''; + + return str_replace("\n", ' ', $tooltip); + } } diff --git a/src/FormGenerator/FormElements/ChecklistFormElement.php b/src/FormGenerator/FormElements/ChecklistFormElement.php index e5b17c6..b65fcf4 100644 --- a/src/FormGenerator/FormElements/ChecklistFormElement.php +++ b/src/FormGenerator/FormElements/ChecklistFormElement.php @@ -2,10 +2,55 @@ namespace CosmoCode\Formserver\FormGenerator\FormElements; +use Michelf\MarkdownExtra; + /** * Representation of a checkbox group */ class ChecklistFormElement extends AbstractDynamicFormElement { + /** + * Sets the default value defined in the config. + * If none given do nothing + * + * @return void + */ + public function setDefaultValue() + { + $defaultValue = $this->getConfigValue('default'); + + if ($defaultValue) { + $this->setValue($defaultValue); + } + } + + /** + * Override parent to transform markdown choice labels + * + * @return array + */ + public function getViewVariables() + { + $choices = $this->getConfigValue('choices'); + $transformedChoices = []; + + foreach ($choices as $choice) { + $transformedChoice= MarkdownExtra::defaultTransform($choice); + + // Markdown lib always wraps the content in a

...

+ // https://github.com/michelf/php-markdown/issues/230 + $transformedChoices[] = str_replace( + ['

', '

'], + '', + $transformedChoice + ); + }; + return array_merge( + parent::getViewVariables(), + [ + 'transformed_choices' => $transformedChoices + ] + ); + } } diff --git a/src/FormGenerator/FormElements/DropdownFormElement.php b/src/FormGenerator/FormElements/DropdownFormElement.php index 8555257..9fcd68b 100644 --- a/src/FormGenerator/FormElements/DropdownFormElement.php +++ b/src/FormGenerator/FormElements/DropdownFormElement.php @@ -7,5 +7,18 @@ */ class DropdownFormElement extends AbstractDynamicFormElement { + /** + * Sets the default value defined in the config. + * if none given do nothing + * + * @return void + */ + public function setdefaultvalue() + { + $defaultValue = $this->getConfigValue('default'); + if ($defaultValue) { + $this->setValue($defaultValue); + } + } } diff --git a/src/FormGenerator/FormElements/HrFormElement.php b/src/FormGenerator/FormElements/HrFormElement.php new file mode 100644 index 0000000..c3d3e78 --- /dev/null +++ b/src/FormGenerator/FormElements/HrFormElement.php @@ -0,0 +1,11 @@ +form->getMeta('title'); + $tooltipStyle = $this->form->getMeta('tooltip_style') ?? ''; $saveButtonLabel = LangManager::getString('button_save'); $sendButtonlabel = LangManager::getString('button_send'); $uploadButtonLabel = LangManager::getString('button_upload'); $replaceUploadButtonLabel = LangManager::getString('button_upload_replace'); $uploadedFileLabel = LangManager::getString('uploaded_file'); + $uploadInfo = LangManager::getString('upload_info'); // Global variables available in all templates and macros $this->twig->addGlobal('form_id', $this->form->getId()); @@ -75,6 +77,8 @@ public function render() $this->twig->addGlobal('button_upload_label', $uploadButtonLabel); $this->twig->addGlobal('button_upload_replace', $replaceUploadButtonLabel); $this->twig->addGlobal('uploaded_file_label', $uploadedFileLabel); + $this->twig->addGlobal('upload_info', $uploadInfo); + $this->twig->addGlobal('tooltip_style', $tooltipStyle); foreach ($this->form->getFormElements() as $formElement) { if ($formElement instanceof FieldsetFormElement) { @@ -97,7 +101,8 @@ public function render() 'notification' => $this->generateNotification(), 'css' => $this->form->getMeta('css'), 'form_id' => $this->form->getId(), - 'logo' => $this->form->getMeta('logo') + 'logo' => $this->form->getMeta('logo'), + 'save_button_visible' => $this->form->getMeta('saveButton') ?? true ] ); } diff --git a/src/Helper/FileHelper.php b/src/Helper/FileHelper.php new file mode 100644 index 0000000..c1bc27a --- /dev/null +++ b/src/Helper/FileHelper.php @@ -0,0 +1,55 @@ +exportDir = FileHelper::sanitizeDirectoryPath( + self::ROOT_DIR . $exportConfiguration['dir'] ?? '' + ); + } + + /** + * Move a file to configured export dir with $newFilename + * + * @param string $filePath + * @param string $newFilename + * @return void + * @throws \RuntimeException + */ + public function moveFile(string $filePath, string $newFilename) + { + + if (! is_file($filePath)) { + throw new FormException( + "Could not move file '$filePath'. File does not exist" + ); + } + + if (! mkdir($this->exportDir, 0755, true) && ! is_dir($this->exportDir)) { + throw new FormException( + 'Could not create export directory ' . $this->exportDir + ); + } + + $fileExtension = FileHelper::getFileExtension($filePath); + + $newFilePath = $this->exportDir . $newFilename . '.' . $fileExtension; + if (! is_file($newFilePath)) { + copy($filePath, $newFilePath); + } + } +} diff --git a/src/Service/LangManager.php b/src/Service/LangManager.php index 6130df5..85c7331 100644 --- a/src/Service/LangManager.php +++ b/src/Service/LangManager.php @@ -2,6 +2,7 @@ namespace CosmoCode\Formserver\Service; +use CosmoCode\Formserver\Exceptions\LanguageException; use CosmoCode\Formserver\Helper\YamlHelper; /** @@ -11,7 +12,8 @@ class LangManager { const LANG_FILE_PATH_GLOBAL = __DIR__ . '/../../conf/language.default.yaml'; const LANG_FILE_PATH_LOCAL = __DIR__ . '/../../conf/language.local.yaml'; - + const LANG_FILE_PATH_DYNAMIC = __DIR__ . '/../../conf/language.{{language}}.yaml'; + /** * Language strings. * All defaults from conf/language.default.yaml can be overridden @@ -21,26 +23,59 @@ class LangManager */ protected static $translations; + /** - * Returns a language string for a given string id + * Initialize LangManager - load specific language * - * @param string $id - * @return string + * @param string|null $language + * @return void + * @throws LanguageException */ - public static function getString($id) + public static function init(string $language = null) { - if (! self::$translations) { - self::$translations = YamlHelper::parseYaml(self::LANG_FILE_PATH_GLOBAL); - // allow overriding of app defaults - $localFile = self::LANG_FILE_PATH_LOCAL; - if (is_file($localFile)) { - self::$translations = array_replace_recursive( - self::$translations, - YamlHelper::parseYaml($localFile) + if (! empty(self::$translations)) { + throw new LanguageException('LangManager already initialized.'); + } + + $translations = YamlHelper::parseYaml(self::LANG_FILE_PATH_GLOBAL); + + if (is_file(self::LANG_FILE_PATH_LOCAL)) { + $translations = array_replace_recursive( + $translations, + YamlHelper::parseYaml(self::LANG_FILE_PATH_LOCAL) + ); + } + + if (! empty($language)) { + $languageFile = str_replace( + '{{language}}', + $language, + self::LANG_FILE_PATH_DYNAMIC + ); + + if (! is_file($languageFile)) { + throw new LanguageException( + "LangManager could not load language file: '$languageFile'" ); } + + $translations = array_replace_recursive( + $translations, + YamlHelper::parseYaml($languageFile) + ); } + self::$translations = $translations; + } + + /** + * Returns a language string for a given string id + * + * @param string $id + * @return string + */ + public static function getString(string $id) + { return self::$translations[$id] ?? ''; } } diff --git a/src/Service/Mailer.php b/src/Service/Mailer.php index d698611..f26270e 100644 --- a/src/Service/Mailer.php +++ b/src/Service/Mailer.php @@ -125,7 +125,7 @@ public function sendForm(Form $form) protected function formToMessage( array $formElements, string $formDirectory, - string $title = '' + string $title = null ) { $htmlHeadline = '

%s

'; $textHeadline = "\n\n%s\n\n"; @@ -142,7 +142,9 @@ protected function formToMessage( * @var AbstractDynamicFormElement $element */ foreach ($formElements as $element) { - if ($element instanceof FieldsetFormElement && ! $element->isDisabled()) { + if ($element instanceof FieldsetFormElement + && ! $element->isDisabled() + ) { $this->textBody .= sprintf($textHeadline, $element->getConfigValue('label')); $this->htmlBody diff --git a/view/_form.twig b/view/_form.twig index c999fea..7843690 100644 --- a/view/_form.twig +++ b/view/_form.twig @@ -6,6 +6,7 @@ {{ title is defined ? title : 'Form'}} + {%- if css is not empty -%} @@ -39,11 +40,13 @@
-
- -
+ {% if save_button_visible %} +
+ +
+ {% endif %}
{%- endif -%} +{%- endmacro -%} + +{%- macro renderTooltip(tooltip) -%} + {%- if tooltip is not empty -%} + + {%- endif -%} {%- endmacro -%} \ No newline at end of file diff --git a/view/checklist.twig b/view/checklist.twig index a8aba48..6de2567 100644 --- a/view/checklist.twig +++ b/view/checklist.twig @@ -1,8 +1,9 @@ -{% from '_macros' import renderError %} +{% from '_macros' import renderError, renderTooltip %}
{{ label }} {{ is_required ? '*' }}
+ {{ renderTooltip(tooltip) }}
{%- for choice in choices -%} {%- if alignment is defined and alignment == 'vertical' -%}
diff --git a/view/date.twig b/view/date.twig index 370ae36..bf99ecb 100644 --- a/view/date.twig +++ b/view/date.twig @@ -1,7 +1,8 @@ -{% from '_macros' import renderError %} +{% from '_macros' import renderError, renderTooltip %}
+ {{ renderTooltip(tooltip) }}
+ {{ renderTooltip(tooltip) }}
+ {{ renderTooltip(tooltip) }}
+
@@ -12,8 +13,4 @@ {% endfor %}
- -
- - diff --git a/view/hr.twig b/view/hr.twig new file mode 100644 index 0000000..2f8de41 --- /dev/null +++ b/view/hr.twig @@ -0,0 +1,4 @@ +{% from '_macros' import fileUrl , renderError %} +
+
+
diff --git a/view/numberinput.twig b/view/numberinput.twig index 5130703..0088905 100644 --- a/view/numberinput.twig +++ b/view/numberinput.twig @@ -1,7 +1,8 @@ -{% from '_macros' import renderError %} +{% from '_macros' import renderError, renderTooltip %}
+ {{ renderTooltip(tooltip) }}
{{ label }} {{ is_required ? '*' }}
+ {{ renderTooltip(tooltip) }}
{%- for choice in choices -%}