Version v0.12.0

This commit is contained in:
Johannes Meyer 2023-05-15 08:39:32 +00:00
parent 7610682215
commit fc33c42196
579 changed files with 408908 additions and 0 deletions

1
VERSION Normal file
View File

@ -0,0 +1 @@
v0.12.0

View File

@ -0,0 +1,35 @@
:root, :host {
--fa-font-regular: normal 400 1em/1 "Font Awesome 6 Free";
--fa-font-solid: normal 900 1em/1 "Font Awesome 6 Free";
}
@font-face {
font-family: 'Font Awesome 6 Free';
font-style: normal;
font-weight: 400;
font-display: block;
src: url('@{iplWebAssets}/font/awesome/fa-regular-400.woff2') format('woff2'),
url('@{iplWebAssets}/font/awesome/fa-regular-400.ttf') format('truetype');
}
.far,
.fa-regular {
font-family: 'Font Awesome 6 Free';
font-weight: 400;
}
@font-face {
font-family: 'Font Awesome 6 Free';
font-style: normal;
font-weight: 900;
font-display: block;
src: url('@{iplWebAssets}/font/awesome/fa-solid-900.woff2') format('woff2'),
url('@{iplWebAssets}/font/awesome/fa-solid-900.ttf') format('truetype');
}
.fa,
.fas,
.fa-solid {
font-family: 'Font Awesome 6 Free';
font-weight: 900;
}

148
asset/css/balls.less Normal file
View File

@ -0,0 +1,148 @@
@ball-pad: 1/6em;
.ball {
border-radius: 50%;
display: inline-block;
text-align: center;
}
.ball-size-xs {
height: 1/3em;
width: 1/3em;
}
.ball-size-s {
height: 0.5em;
width: 0.5em;
}
.ball-size-m {
height: 0.75em;
width: 0.75em;
line-height: 0;
i.icon:before {
font-size: .75 - @ball-pad * 2;
line-height: 1em;
}
}
.ball-size-ml {
height: 1em;
width: 1em;
line-height: 0;
i.icon {
line-height: 0.3;
&:before {
font-size: 0.8 - @ball-pad * 2;
line-height: 1 - @ball-pad * 2;
}
}
}
.ball-size-l {
height: 1.5em;
width: 1.5em;
line-height: 1em;
i.icon:before {
font-size: 1 - @ball-pad * 2;
line-height: 1.5 - @ball-pad * 2;
}
}
.ball-size-xl {
width: 2em;
height: 2em;
i.icon:before {
line-height: 2 - @ball-pad * 2;
}
}
.ball-outline(@color) {
border: @ball-pad solid @color;
color: @color;
}
.ball-solid(@color) {
background-color: @color;
color: var(--default-text-color-inverted, @default-text-color-inverted);
padding: @ball-pad;
}
.state-ball {
.ball();
&.state-pending:not(.ball-size-l):not(.ball-size-xl) {
.ball-solid(var(--state-pending, @state-pending));
}
&.state-pending.ball-size-l,
&.state-pending.ball-size-xl {
.ball-outline(var(--state-pending, @state-pending));
}
&.state-up:not(.ball-size-l):not(.ball-size-xl) {
.ball-solid(var(--state-up, @state-up));
}
&.state-up.ball-size-l,
&.state-up.ball-size-xl {
.ball-outline(var(--state-up, @state-up));
}
&.state-down {
.ball-solid(var(--state-down, @state-down));
}
&.state-ok:not(.ball-size-l):not(.ball-size-xl) {
.ball-solid(var(--state-ok, @state-ok));
}
&.state-ok.ball-size-l,
&.state-ok.ball-size-xl {
.ball-outline(var(--state-ok, @state-ok));
}
&.state-warning {
.ball-solid(var(--state-warning, @state-warning));
}
&.state-critical {
.ball-solid(var(--state-critical, @state-critical));
}
&.state-unknown {
.ball-solid(var(--state-unknown, @state-unknown));
}
&.handled {
opacity: 0.6;
}
i {
text-align: center;
display: block;
&:before {
margin-right: 0;
}
}
// Specific icon styles
&.ball-size-l i {
&.fa-sitemap:before {
font-size: 8px; // px to ignore browser min font-size
}
}
&.ball-size-xl i {
&.fa-sitemap:before {
font-size: .857em;
line-height: (2 - @ball-pad * 2) / .857;
}
}
}

View File

@ -0,0 +1,34 @@
.cancel-button {
display: inline-flex;
align-items: baseline;
padding: .5em 1em;
.appearance(none);
.rounded-corners();
line-height: normal;
cursor: pointer;
background: var(--cancel-button-bg, @cancel-button-bg);
border: 1px solid var(--cancel-button-border-color, @cancel-button-border-color);
color: var(--cancel-button-color, @cancel-button-color);
&:focus,
&:hover {
background-color: var(--cancel-button-hover-bg, @cancel-button-hover-bg);
color: var(--cancel-button-hover-color, @cancel-button-hover-color);
}
&[disabled] {
background: none;
cursor: default;
border: 1px solid var(--control-disabled-color, @control-disabled-color);
color: var(--control-disabled-color, @control-disabled-color);
&:focus,
&:hover {
background: none;
color: var(--control-disabled-color, @control-disabled-color);
}
}
}

84
asset/css/compat.less Normal file
View File

@ -0,0 +1,84 @@
// General input styles
.icinga-controls {
.uploaded-files {
background-color: @default-input-bg;
}
}
form.icinga-form {
.uploaded-files {
flex: 1 1 auto;
width: 0;
}
}
.icinga-controls {
.uploaded-files {
font-size: inherit;
padding: .5em;
}
}
// Button styles
// The `form` selector is only required to overrule the hover effect applied by Icinga Web.
// It's not required if done by Icinga Web itself, only here because this is applied earlier
// as it's part of a library.
form.icinga-controls {
button[type="submit"].remove-uploaded-file {
all: unset;
}
}
// Schedule Element styles
.icinga-form > .schedule-element,
.icinga-form > .schedule-element > fieldset {
margin-top: 1em;
> .control-group:first-child {
margin-top: 0;
}
}
.icinga-form .schedule-element {
.control-group > fieldset > .weekly,
.control-group > .ordinal,
.control-group > .monthly,
.control-group > .annually {
flex: 1 1 auto;
}
// TODO: This effectively restricts the weekly fields to always be aligned to the right,
// regardless of the using an icinga-form or not. So this should be removed once we
// have re-implemented the decorators.
.control-group > fieldset > .weekly {
margin-left: 14em;
}
}
form.icinga-form .control-group {
> .monthly,
> .ordinal {
margin-right: 2em;
}
> .ordinal.annually {
margin-right: 1em;
}
}
// TermInput styles
form.icinga-form .control-group {
> .term-input-area {
flex: 1 1 auto;
width: auto;
input[type="text"] {
flex: unset;
width: 100%;
}
}
}

151
asset/css/controls.less Normal file
View File

@ -0,0 +1,151 @@
.pagination-control {
li > a {
color: var(--control-color, @control-color);
border-radius: .25em;
}
li > a:hover {
background: var(--control-hover-bg, @control-hover-bg);
}
.previous-page,
.next-page {
padding: .5em .25em;
i {
display: block;
}
i:before {
margin: 0;
}
}
.previous-page > i {
margin-left: -.125em;
}
.next-page > i {
margin-right: -.125em;
}
}
// Style
.control-button {
.appearance(none);
background: none;
border: none;
color: var(--control-color, @control-color);
.rounded-corners();
&:hover, &:focus, &.active {
background-color: var(--control-hover-bg, @control-hover-bg);
text-decoration: none;
}
&.disabled {
color: var(--control-disabled-color, @control-disabled-color);
&:hover {
background: none;
}
}
i.icon:before {
color: inherit;
}
}
// Layout
.control-button {
display: inline-block;
padding: .25em .5em;
> i.icon {
display: inline-flex;
align-items: center;
height: 100%;
}
i.icon:before {
margin-right: 0;
}
}
.sort-control {
display: flex;
justify-content: flex-end;
.form-element {
display: inline-flex;
align-items: baseline;
margin-right: .5em;
label {
margin-right: .5em;
}
}
.control-button {
margin: 0;
}
}
.search-controls {
display: flex;
min-width: 100%;
.search-bar {
flex: 1 1 auto;
& ~ .control-button {
margin-left: .5em;
}
}
}
/**
The default layout of list controls in Icinga Web
┌────────────────────────────────────────────────────────────────┐
│ .pagination-control .limit-control .sort-control │
│ <-------------------- .search-controls ----------------------> │
└────────────────────────────────────────────────────────────────┘
*/
.controls.default-layout {
.box-shadow(0, 0, 0, 1px, @gray-lighter);
z-index: 1; // The content may clip, this ensures the separator is always visible
> .pagination-control {
float: left;
}
> .sort-control,
> .limit-control {
float: right;
}
> .limit-control {
margin-right: .5em;
}
> .search-controls {
clear: both;
}
> :not(:only-child) {
margin-bottom: 0.5em;
}
> .sort-control,
> .search-controls > .control-button:last-child {
margin-right: -.5em;
}
> .search-controls > .search-bar .search-suggestions {
// Suggestions should be kept at a distance from the bottom of the page
margin-bottom: 2.5em;
}
}

View File

@ -0,0 +1,9 @@
.flatpickr-input + .input {
padding-right: 2em;
& + .fa-calendar {
margin: .5em 1em 0 -3.5em;
padding: 0 .5em 0 1em;
pointer-events: none;
}
}

View File

@ -0,0 +1,40 @@
form .uploaded-files {
list-style-type: none;
padding: 0;
margin: 0;
> li:not(:last-of-type) {
margin-bottom: .5em;
}
button[type="submit"].remove-uploaded-file {
.icon {
font-size: 1.2em;
}
&:focus, &:hover {
cursor: pointer;
.icon {
color: red;
}
}
}
// text-overflow: ellipsis layout rules, yes, exclusively
> li {
display: flex;
> button[type="submit"].remove-uploaded-file {
display: inline-flex;
flex: 1 1 auto;
width: 0;
> span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}

6372
asset/css/fontawesome.css vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
.horizontal-key-value {
display: flex;
padding: .25em 0;
align-items: baseline;
.key {
color: var(--default-text-color-light, @default-text-color-light);
flex: 0 0 auto;
white-space: nowrap;
width: 12em;
}
.value {
color: var(--default-text-color, @default-text-color);
flex: 1 1 auto;
}
}

View File

@ -0,0 +1,39 @@
@font-face {
font-family: 'Icinga-Icons';
src: url('@{iplWebAssets}/font/Icinga-Icons.eot');
src: url('@{iplWebAssets}/font/Icinga-Icons.eot') format('embedded-opentype'),
url('@{iplWebAssets}/font/Icinga-Icons.ttf') format('truetype'),
url('@{iplWebAssets}/font/Icinga-Icons.woff') format('woff'),
url('@{iplWebAssets}/font/Icinga-Icons.svg') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
}
[class^="iicon-"]:before, [class*=" iicon-"]:before {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'Icinga-Icons';
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1em;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.iicon-minimal:before {
content: "\e900";
}
.iicon-detailed:before {
content: "\e901";
}
.iicon-default:before {
content: "\e902";
}
.iicon-icinga:before {
content: "\e907";
}

12
asset/css/icons-base.less Normal file
View File

@ -0,0 +1,12 @@
i.icon {
vertical-align: middle; // Firefox will place icons weird otherwise
&:before {
display: inline-block;
min-width: 1em;
margin-right: .2em;
text-align: center;
text-decoration: inherit;
}
}

23
asset/css/mixin/card.less Normal file
View File

@ -0,0 +1,23 @@
.card() {
&.card {
.rounded-corners(.5em);
border: 1px solid var(--card-border-color, @card-border-color);
.card-header {
display: flex;
align-items: baseline;
justify-content: space-between;
padding: .5em;
border-bottom: 1px solid var(--card-border-color, @card-border-color);
.meta span {
font-size: 11/12em;
}
}
.card-body {
padding: .5em;
}
}
}

View File

@ -0,0 +1,20 @@
.rounded-corners(@border-radius: 0.4em) {
border-radius: @border-radius;
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box;
}
.appearance(@appearance) {
-webkit-appearance: @appearance;
-moz-appearance: @appearance;
-ms-appearance: @appearance;
appearance: @appearance;
}
.box-shadow(@x: 0.2em; @y: 0.2em; @blur: 0.2em; @spread: 0; @color: rgba(83, 83, 83, 0.25)) {
-webkit-box-shadow: @arguments;
-moz-box-shadow: @arguments;
box-shadow: @arguments;
}

View File

@ -0,0 +1,31 @@
.state-badges() {
&.state-badges {
padding: 0;
ul {
padding: 0;
}
li {
display: inline-block;
}
li > ul > li:first-child:not(:last-child) .state-badge {
border-bottom-right-radius: 0;
border-top-right-radius: 0;
}
li > ul > li:last-child:not(:first-child) .state-badge {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
}
> li:not(:last-child) {
margin-right: .25em;
}
li > ul > li:last-child {
margin-left: 1px;
}
}
}

View File

@ -0,0 +1,16 @@
/**
Automatically set CSS class for duplicated submit buttons
used for implicit form submission that should be invisible
and not take up any space. `display: none` is not an option,
because at least Safari will then ignore the element completely
when submitting a form.
*/
.primary-submit-btn-duplicate {
border: 0;
height: 0;
margin: 0;
padding: 0;
visibility: hidden;
width: 0;
position: absolute;
}

View File

@ -0,0 +1,210 @@
// Schedule form element
.schedule-element {
@input-border-radius: .25em;
.ordinal {
display: flex;
flex-wrap: wrap;
.radio-label {
flex: 1 1 auto;
}
select {
flex: 1 1 auto;
&:first-of-type {
margin-right: 1em;
}
&:disabled {
color: @schedule-element-fields-disabled-color;
}
}
}
.radio-label {
width: 100%;
margin-bottom: .5em;
display: flex;
align-items: center; // To center the radio element on safari
}
.number-specifier > input[type="number"] {
width: 5em;
margin: 0 1em;
}
.monthly, .ordinal:not(.annually) {
padding: .5em;
margin-left: -.5em;
border: 1px solid @schedule-element-fields-border-color;
.rounded-corners(.75em);
}
.schedule-element-fields {
list-style-type: none;
margin: 0;
padding: 0;
.rounded-corners(.25em);
overflow: hidden;
display:flex;
flex-wrap: wrap;
&.disabled { // When the "On the" radio button is checked
pointer-events: none;
label {
color: @schedule-element-fields-disabled-color;
background-color: @schedule-element-fields-disabled-bg;
}
input:checked + label {
background: @schedule-element-fields-disabled-selected-bg;
color: @schedule-element-fields-disabled-color;
}
}
li {
width: calc(100% / 7); /* default for week based cols makes sense */
label {
display: block;
width: 100%;
cursor: pointer;
text-align: center;
padding: .75em 0;
background: @schedule-element-fields-bg;
color: @schedule-element-fields-color;
&:hover {
background-color: @schedule-element-fields-hover-bg;
}
&:focus {
outline: none;
}
}
input:checked + label {
background-color: @schedule-element-fields-selected-bg;
color: @schedule-element-fields-selected-color;
}
input:checked + label:hover {
background-color: @schedule-element-fields-selected-hover-bg;
border-color: @schedule-element-fields-selected-hover-bg;
}
}
&.multiple-fields {
li:not(:last-child) label {
border-right: 1px solid @schedule-element-fields-border-color;
}
input:focus + label {
box-shadow: inset 0 0 0 3px @schedule-element-fields-outline-color;
}
input:checked:focus + label {
box-shadow: inset 0 0 0 3px @schedule-element-fields-selected-outline-color;
}
}
&.single-fields {
li {
padding-right: 1px;
}
li label {
.rounded-corners(.25em);
margin-right: 1px;
margin-bottom: 1px;
}
li label {
border-right: none;
}
&:focus-within {
outline: 3px solid @schedule-element-fields-outline-color;
outline-offset: 2px;
}
&:focus-within + .note {
display: block;
}
input:checked + label:hover {
background-color: @schedule-element-fields-selected-bg;
}
}
}
.note {
display: none;
padding: .5em;
background: @schedule-element-keyboard-note-bg;
.rounded-corners(.25em);
text-align: center;
margin-top: 1em;
line-height: 1.25;
}
/* .weekly */
.weekly { }
/* .monthly styles */
.monthly {
li label {
border-top: 1px solid @schedule-element-fields-border-color;
}
li:first-child,
li:nth-child(2),
li:nth-child(3),
li:nth-child(4),
li:nth-child(5),
li:nth-child(6),
li:nth-child(7) {
label {
border-top: none;
}
}
/* last of row should not have a border */
.schedule-element-fields li:nth-child(7n) label {
border-right: none;
}
}
/* .annually styles */
.annually {
li {
width: 25%; // 100% / 4 elements
}
li:nth-child(4n) label {
margin-right: 0;
}
.toggle-slider-controls {
display: flex;
column-gap: 1em;
align-items: center;
margin-top: 1em;
margin-bottom: -.6em;
}
}
}
.schedule-recurrences {
line-height: 1.1em;
padding-top: 0.5625em;
p {
color: @schedule-element-fields-disabled-color;
}
}

232
asset/css/search-bar.less Normal file
View File

@ -0,0 +1,232 @@
// Style
.search-bar {
.rounded-corners(.25em);
background: var(--searchbar-bg, @searchbar-bg);
// Reset all input styles
input, [type="button"] {
.appearance(none);
border: none;
background: none;
}
// Submit button styles
input[type=submit],
button[type=submit],
button:not([type]) {
background: var(--primary-button-bg, @primary-button-bg);
color: var(--primary-button-color, @primary-button-color);
border-top-right-radius: .25em;
border-bottom-right-radius: .25em;
}
// General input styles
input:focus {
outline-offset: -1px;
}
// Hide the submit button, it must exist, but shouldn't be shown to the user
input[type=submit][value="hidden"] {
display: none;
}
// Left-most search dropdown style
button.search-options {
i.icon:before {
font-size: 1.2em;
margin-right: 0;
color: var(--control-color, @control-color);
}
&:disabled {
i.icon:before {
color: var(--control-disabled-color, @control-disabled-color);
}
}
}
// Term styles
.filter-condition {
button {
border-radius: .4em 0 0 .4em;
background-color: var(--search-condition-remove-bg, @search-condition-remove-bg);
color: var(--search-condition-remove-color, @search-condition-remove-color);
&:after {
content: "";
position: absolute;
width: .4em;
height: 100%;
right: 0;
top: 0;
background-color: var(--searchbar-bg, @searchbar-bg);
border: .2em solid var(--search-condition-remove-bg, @search-condition-remove-bg);
border-width: 0 0 0 .2em;
border-top-left-radius: .4em;
border-bottom-left-radius: .4em;
}
}
input {
background-color: var(--search-term-bg, @search-term-bg);
color: var(--search-term-color, @search-term-color);
}
}
.terms > .filter-condition:first-child button {
border-radius: 0 .4em .4em 0;
&:before {
content: "";
position: absolute;
width: .4em;
height: 100%;
left: 0;
top: 0;
background-color: var(--searchbar-bg, @searchbar-bg);
border: .2em solid var(--search-condition-remove-bg, @search-condition-remove-bg);
border-width: 0 .2em 0 0;
border-top-right-radius: .4em;
border-bottom-right-radius: .4em;
}
&:after {
content: none;
}
}
.logical_operator,
.grouping_operator_open,
.grouping_operator_close {
input {
.rounded-corners();
background-color: var(--search-logical-operator-bg, @search-logical-operator-bg);
color: var(--search-logical-operator-color, @search-logical-operator-color);
}
}
.operator,
.logical_operator,
.grouping_operator_open,
.grouping_operator_close {
input {
text-align: center;
}
}
.column input {
.rounded-corners(.4em);
}
.column:not(:last-of-type),
.column.last-term {
input {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
.operator:last-of-type:not(.last-term) input {
.rounded-corners(.4em);
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.value input {
.rounded-corners(.4em);
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.highlighted input {
background-color: var(--search-term-highlighted-bg, @search-term-highlighted-bg);
color: var(--search-term-highlighted-color, @search-term-highlighted-color);
}
ul.comma-separated {
display: inline;
padding: 0;
list-style-type: none;
li {
display: inline;
&:not(:first-of-type):before {
display: inline;
content: ', ';
}
}
}
}
// Layout
.search-bar {
height: 2em;
display: flex;
position: relative; // Required for the suggestions
button.search-options {
line-height: 1em;
}
.filter-input-area {
padding: 2/12em; // 2 (px) desired / default font size (px)
}
.terms {
.filter-chain,
.filter-condition {
display: inline;
}
.filter-condition {
position: relative;
button {
display: none;
z-index: 1;
width: ~"calc(2em + 2px)";
padding: .15em .6em .15em .4em;
position: absolute;
left: ~"calc(-2em - 2px)"; // That's min-width + margin-right of an operator
line-height: 16/12; // 16 (px) desired / default font size (px)
i:before {
margin-right: 0;
}
}
&:not(._hover_delay):hover button {
display: inline;
}
}
> .filter-condition:first-child button {
padding: .15em .4em .15em .6em;
left: auto;
right: ~"calc(-2em - 1px)"; // That's min-width + margin-left of an operator
}
label {
&.logical_operator,
&.grouping_operator_open,
&.grouping_operator_close {
margin-left: 1px; // adds up to 2px with the previous term
margin-right: 2px;
}
}
}
&.disabled {
.terms .filter-condition:hover button {
display: none;
}
}
.search-suggestions {
// 2 (px) desired / default font-size to match .filter-input outline-offset (-1px) + outline-width (3px)
margin-top: 2/12em;
}
}

240
asset/css/search-base.less Normal file
View File

@ -0,0 +1,240 @@
// Style
.search-bar .filter-input-area,
.term-input-area {
// Scrollbar style
// Firefox
scrollbar-width: thin;
scrollbar-color: var(--searchbar-scrollbar-bg, @searchbar-scrollbar-bg) transparent;
&::-webkit-scrollbar {
display: none;
height: .5em;
}
&:hover::-webkit-scrollbar {
display: initial;
}
&::-webkit-scrollbar-thumb {
border-radius: .25em;
background: var(--searchbar-scrollbar-bg, @searchbar-scrollbar-bg);
}
}
.search-bar,
.term-input-area {
[data-index] input:invalid {
background-color: var(--search-term-invalid-bg, @search-term-invalid-bg);
color: var(--search-term-invalid-color, @search-term-invalid-color);
}
[data-index] input:disabled {
background-color: var(--search-term-disabled-bg, @search-term-disabled-bg);
}
.selected input {
background-color: var(--search-term-selected-bg, @search-term-selected-bg);
color: var(--search-term-selected-color, @search-term-selected-color);
font-style: italic;
}
}
.search-suggestions {
background: var(--suggestions-bg, @suggestions-bg);
border: 1px solid var(--suggestions-border-color, @suggestions-border-color);
border-bottom-right-radius: .5em;
border-bottom-left-radius: .5em;
> ul {
list-style-type: none;
> li {
border-top: 1px solid var(--suggestions-separation-bg, @suggestions-separation-bg);
}
> li.suggestion-title + li {
border: none;
}
> li:not(.default) + li.suggestion-title {
border: none;
}
}
.default {
color: var(--suggestions-default-opt-color, @suggestions-default-opt-color);
font-style: italic;
[type="button"] {
background-color: var(--suggestions-default-opt-bg, @suggestions-default-opt-bg);
}
}
.suggestion-title {
color: var(--suggestions-color, @suggestions-color);
font-size: 80%;
}
.failure-message {
font-weight: bold;
em {
font-weight: normal;
color: var(--suggestions-failure-message-color, @suggestions-failure-message-color);
}
}
.nothing-to-suggest {
color: var(--suggestions-color, @suggestions-color);
}
.relation-path {
padding: 0 .2em;
background-color: var(--suggestions-relation-path-bg, @suggestions-relation-path-bg);
}
[type="button"] {
.appearance(none);
border: none;
background: none;
}
[type="button"]:focus {
background: var(--suggestions-focus-bg, @suggestions-focus-bg);
color: var(--suggestions-focus-color, @suggestions-focus-color);
outline: none;
.relation-path {
background-color: var(--suggestions-relation-path-focus-bg, @suggestions-relation-path-focus-bg);
}
}
[type="button"]:not(:focus):hover {
background: var(--suggestions-hover-bg, @suggestions-hover-bg);
}
}
// Layout
.search-bar .filter-input-area,
.term-input-area {
overflow: auto hidden;
overflow-x: overlay; // Not invalid, but proprietary feature by chrome/webkit
display: flex;
flex-wrap: nowrap;
width: 100%;
height: ~"calc(2em + 10px)"; // Search bar height + approximate scrollbar height
// Lets inputs grow based on their contents, Inspired by https://css-tricks.com/auto-growing-inputs-textareas/
label {
position: relative;
display: inline-block;
min-width: 2em;
height: 100%;
&::after,
input {
width: auto;
padding: 0 .5em;
resize: none;
}
input {
width: 100%;
position: absolute;
top: 0;
line-height: 20/12; // 20 (px) desired / default font size (px)
}
&::after {
height: 0;
content: attr(data-label);
visibility: hidden;
white-space: nowrap;
padding: 0 7/12em; // 7 (px) desired / default font size (px)
}
}
> label {
flex: 1 0 auto;
&::after,
input {
max-width: none;
min-width: 8em;
}
}
> .terms {
display: inline;
flex-shrink: 0;
label {
margin-right: 1px;
}
}
}
.term-input-area {
label input:focus {
@labelPad: 7/12em;
outline-width: 3px;
outline-offset: ~"calc(-@{labelPad} + 3px)";
}
}
.search-suggestions {
z-index: 2; // Required so that nothing else can overlap it (such as opaque elements and the impact overlay)
position: absolute;
overflow: auto;
min-width: 5em;
&:empty {
display: none;
}
> ul {
margin: 0;
padding: 0;
li.suggestion-title {
padding: 1.25em .625em 0 .625em;
}
li.failure-message {
padding: .5em 1em;
em {
margin-right: .5em;
}
}
li.nothing-to-suggest {
padding: .5em 1em;
}
}
[type="button"] {
padding: .5em 1em;
display: block;
width: 100%;
text-align: left;
&[data-class="operator"], &[data-class="logical_operator"] {
text-align: center;
}
&.has-details {
display: flex;
align-items: baseline;
justify-content: space-between;
}
.relation-path {
margin-left: .5em;
&::first-line {
font-size: .8em;
}
}
}
}

View File

@ -0,0 +1,268 @@
// Style
.search-editor {
ul, ol {
list-style-type: none;
}
fieldset {
border: none;
}
button, input[type="submit"] {
.appearance(none);
background: none;
&:not(.cancel-button) {
border: none;
}
}
select:not([multiple]) {
.appearance(none);
padding-right: 1.5625em;
background-image: url('@{iplWebAssets}/img/select-icon-text-color.svg');
background-repeat: no-repeat;
background-position: right center;
background-size: contain;
.rounded-corners(0);
}
i.icon:before {
color: var(--search-editor-control-color, @search-editor-control-color);
}
.drag-initiator {
cursor: grab;
}
input[type="text"], select {
border: none;
background: var(--search-term-bg, @search-term-bg);
color: var(--search-term-color, @search-term-color);
text-overflow: ellipsis;
}
:not(fieldset) > select {
.rounded-corners();
}
fieldset > input[data-type="column"] {
.rounded-corners(.4em 0 0 .4em);
}
fieldset > input[data-type="value"] {
.rounded-corners(0 .4em .4em 0);
}
.search-error {
input:invalid {
background: var(--search-term-invalid-bg, @search-term-invalid-bg);
color: var(--search-term-invalid-color, @search-term-invalid-color);
}
.search-errors {
color: var(--search-editor-error-color, @search-editor-error-color);
font-weight: bold;
}
}
li > select:not([multiple]) {
background-color: var(--search-logical-operator-bg, @search-logical-operator-bg);
color: var(--search-logical-operator-color, @search-logical-operator-color);
.rounded-corners();
}
.sortable-ghost {
border: dashed .2em var(--search-editor-drag-outline-color, @search-editor-drag-outline-color);
fieldset {
opacity: .5;
}
}
.buttons {
ul {
.rounded-corners();
.box-shadow(0, 0, .5em);
border: 1px solid var(--search-editor-context-menu-border-color, @search-editor-context-menu-border-color);
background: var(--search-editor-context-menu-bg, @search-editor-context-menu-bg);
li:not(:first-child) {
border-top: 1px solid var(--search-editor-context-menu-border-color, @search-editor-context-menu-border-color);
}
button:hover {
background: var(--primary-button-bg, @primary-button-bg);
color: var(--primary-button-color, @primary-button-color);
}
// Add rounded corners to buttons as well, otherwise their
// background is not rounded and overlaps the list's corners
:first-child button {
.rounded-corners();
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
:last-child button {
.rounded-corners();
border-top-left-radius: 0;
border-top-right-radius: 0;
}
&:before {
// The left pointing arrow
border-bottom: 1px solid var(--search-editor-context-menu-border-color, @search-editor-context-menu-border-color);
border-left: 1px solid var(--search-editor-context-menu-border-color, @search-editor-context-menu-border-color);
background: var(--search-editor-context-menu-bg, @search-editor-context-menu-bg);
}
}
&:hover i.icon:before {
.rounded-corners();
background: var(--primary-button-bg, @primary-button-bg);
color: var(--primary-button-color, @primary-button-color);
}
}
input[type="submit"] {
.rounded-corners();
background: var(--primary-button-bg, @primary-button-bg);
color: var(--primary-button-color, @primary-button-color);
&:hover {
background: var(--primary-button-hover-bg, @primary-button-hover-bg);
}
}
}
// Layout
.search-editor-opener + a.modal-opener {
display: none;
}
.search-editor {
padding: 1em;
@item-spacing: .5em;
ul, ol {
width: 100%;
margin: 0;
padding: 0;
}
li {
display: flex;
> :not(:first-child) {
margin-left: @item-spacing;
}
}
ol {
padding-left: 1em;
padding-bottom: @item-spacing;
> li:first-child,
> :not(.filter-chain) + li {
margin-top: @item-spacing;
}
}
input[type="text"], select {
padding: 0 .5em;
}
li > select {
margin-right: auto;
}
fieldset {
display: flex;
flex: 1 1 auto;
margin: 0;
padding: 0;
input[data-type="value"] {
flex: 1 1 auto;
}
> :not(:first-child) {
margin-left: .1em;
}
}
input, button, select {
height: 28/12em; // Target Pixels @ default font size / default font size
}
.search-errors {
margin-left: .5em;
}
i.icon:before {
margin: 0;
font-size: 1.5em;
line-height: 1.5;
}
.buttons {
position: relative;
ul {
position: absolute;
right: 32/12em; // Target distance @ default font size / default font size
z-index: 1;
width: auto;
padding: 0;
display: none;
button {
z-index: 1;
width: 100%;
text-align: left;
white-space: nowrap;
}
&:before {
// The left pointing arrow
content: "";
display: block;
height: 1em;
transform: rotate(-135deg);
width: 1em;
z-index: 1;
position: absolute;
top: ((28/12)/2)-.5em; // ((First row pixels @ default font size / default font size) / 2) - own half width
right: -.5em;
}
}
&:hover ul {
display: block;
}
i.icon:before {
padding: ((28/18)-1)/2em; // (Container pixels / default font size) - line height / (padding-top,padding-bottom)
line-height: 1;
}
}
.cancel-button {
margin-top: 2em - @item-spacing;
}
input[type="submit"] {
float: right;
width: 6em;
margin-top: 2em - @item-spacing;
}
input[type="submit"]:not(:last-of-type) {
display: none;
}
}

View File

@ -0,0 +1,47 @@
.state-badge {
.rounded-corners();
color: var(--default-text-color-inverted, @default-text-color-inverted);
display: inline-block;
font-size: 1em;
min-width: 2em;
padding: .25em;
text-align: center;
&.handled {
opacity: .8;
}
&.state-critical {
background-color: var(--state-critical, @state-critical);
}
&.state-down {
background-color: var(--state-down, @state-down);
}
&.state-ok {
background-color: var(--state-ok, @state-ok);
}
&.state-pending {
background-color: var(--state-pending, @state-pending);
}
&.state-unknown {
background-color: var(--state-unknown, @state-unknown);
}
&.state-up {
background-color: var(--state-up, @state-up);
}
&.state-warning {
background-color: var(--state-warning, @state-warning);
}
}
a .state-badge {
&:not(.disabled):hover {
filter: brightness(80%);
}
}

200
asset/css/variables.less Normal file
View File

@ -0,0 +1,200 @@
/*
RECOMMENDATION:
Please do not use the base color variables directly,
define a new variable instead that assigns the value of this base variable.
Examples:
- @base-color: red;
- @my-new-var: @base-color;
- @my-second-new-var: @base-color;
Do not use the same variable for different use cases, but define a new variable for each use case.
NOTICE:
Color vars identification:
- Vars with `-bg` suffix are background-color vars. Please use only for setting bg-color.
- Vars with `-color` suffix are color vars. Please use only for setting fg-color.
MODE SUPPORT:
The standard LESS variables represent the dark mode. The LESS detached ruleset `@iplWebLightRules`
contains CSS variables that represent the light mode. It must be used explicitly to have any effect.
If you use media queries to support modes, just call the ruleset inside your media query:
@media (prefers-color-scheme: light) {
@iplWebLightRules();
}
*/
@default-bg: #282E39;
@base-gray: #c4c4c4;
@base-gray-light: #5c5c5c;
@base-gray-lighter: #4b4b4b;
@base-disabled: #9a9a9a;
@base-primary-color: #00C3ED;
@base-primary-bg: #00C3ED;
@base-primary-dark: #0081a6;
@base-primary-light: fade(@base-primary-bg, 50%);
@default-text-color: #fff;
@default-text-color-light: fade(@default-text-color, 75%);
@default-text-color-inverted: @default-bg;
@default-input-bg: #404d72;
@state-ok: #44bb77;
@state-up: @state-ok;
@state-warning: #ffaa44;
@state-critical: #ff5566;
@state-down: @state-critical;
@state-pending: #77aaff;
@state-unknown: #aa44ff;
@primary-button-color: @default-text-color-inverted;
@primary-button-bg: @base-primary-bg;
@primary-button-hover-bg: @base-primary-dark;
@search-term-bg: @base-gray;
@search-term-color: @default-text-color-inverted;
@search-term-selected-bg: @base-disabled;
@search-term-invalid-bg: @state-critical;
@search-term-invalid-color: @default-text-color-inverted;
@search-term-disabled-bg: @base-disabled;
@search-term-selected-color: @base-gray-light;
@search-term-highlighted-bg: @base-primary-bg;
@search-term-highlighted-color: @default-text-color-inverted;
@search-condition-remove-bg: @state-critical;
@search-condition-remove-color: @default-text-color-inverted;
@search-logical-operator-bg: @base-gray-light;
@search-logical-operator-color: @default-text-color-light;
@searchbar-bg: @default-input-bg;
@searchbar-scrollbar-bg: @base-gray-light;
@search-editor-error-color: @state-critical;
@search-editor-control-color: @base-gray-light;
@search-editor-logical-op-bg: @base-gray-light;
@search-editor-context-menu-border-color: @base-gray-light;
@search-editor-context-menu-bg: @default-bg;
@search-editor-drag-outline-color: @base-gray;
@control-color: @base-primary-color;
@control-hover-bg: @base-gray-lighter;
@control-disabled-color: @base-disabled;
@cancel-button-bg: none;
@cancel-button-border-color: @state-critical;
@cancel-button-color: @state-critical;
@cancel-button-hover-bg: @state-critical;
@cancel-button-hover-color: @default-text-color-inverted;
@suggestions-bg: @default-bg;
@suggestions-color: @default-text-color-light;
@suggestions-focus-bg: @base-primary-bg;
@suggestions-focus-color: @default-text-color-inverted;
@suggestions-default-opt-bg: fade(@base-primary-bg, 10%);
@suggestions-default-opt-color: @default-text-color-light;
@suggestions-hover-bg: fade(@base-primary-bg, 30%);
@suggestions-border-color: @base-gray-light;
@suggestions-separation-bg: @base-gray-lighter;
@suggestions-failure-message-color: @default-text-color-light;
@suggestions-relation-path-bg: @base-gray-light;
@suggestions-relation-path-focus-bg: @base-gray;
@card-border-color: @base-gray-light;
@schedule-element-fields-bg: @default-input-bg;
@schedule-element-fields-color: @base-primary-color;
@schedule-element-fields-border-color: @base-gray-light;
@schedule-element-fields-selected-bg: @primary-button-bg;
@schedule-element-fields-selected-color: @default-text-color-inverted;
@schedule-element-fields-hover-bg: @base-primary-light;
@schedule-element-fields-outline-color: fade(@base-primary-bg, 50%);
@schedule-element-fields-selected-outline-color: fade(#fff, 50%);
@schedule-element-fields-selected-hover-bg: @primary-button-hover-bg;
@schedule-element-fields-disabled-color: @base-gray;
@schedule-element-fields-disabled-bg: @base-gray-lighter;
@schedule-element-fields-disabled-selected-bg: @base-gray-light;
@schedule-element-keyboard-note-bg: @base-gray-light;
@iplWebLightRules: {
:root {
--base-gray: #819398;
--base-gray-light: #d0d3da;
--base-gray-lighter: #e8ecef;
--base-disabled: var(--base-gray-light);
--base-remove-bg: @state-critical;
--default-text-color: #535353;
--default-text-color-light: fade(#535353, 75%); // --default-text-color
--default-text-color-inverted: #F5F9FA;
--default-input-bg: #DEECF1;
--primary-button-color: var(--default-text-color-inverted);
--primary-button-bg: @primary-button-bg;
--primary-button-hover-bg: @primary-button-hover-bg;
--searchbar-bg: var(--default-input-bg);
--searchbar-scrollbar-bg: var(--base-gray-light);
--search-term-bg: var(--base-gray-light);
--search-term-color: var(--default-text-color);
--search-term-selected-bg: var(--base-disabled);
--search-term-invalid-bg: var(--base-remove-bg);
--search-term-invalid-color: var(--default-text-color-inverted);
--search-term-disabled-bg: var(--base-gray-light);
--search-term-selected-color: var(--base-gray);
--search-term-highlighted-bg: var(--primary-button-bg);
--search-term-highlighted-color: var(--default-text-color-inverted);
--search-condition-remove-bg: var(--base-remove-bg);
--search-condition-remove-color: var(--default-text-color-inverted);
--search-logical-operator-bg: fade(#819398, 50%); // --base-gray
--search-logical-operator-color: var(--default-text-color-light);
--search-editor-error-color: var(--base-remove-bg);
--search-editor-control-color: var(--base-gray-light);
--search-editor-logical-op-bg: var(--base-gray-light);
--search-editor-context-menu-border-color: var(--base-gray-light);
--search-editor-context-menu-bg: var(--default-text-color-inverted);
--search-editor-drag-outline-color: var(--base-gray);
--control-color: var(--primary-button-bg);
--control-hover-bg: var(--base-gray-lighter);
--control-disabled-color: var(--base-gray-light);
--cancel-button-hover-color: var(--default-text-color-inverted);
--suggestions-bg: var(--default-text-color-inverted);
--suggestions-color: var(--default-text-color-light);
--suggestions-focus-bg: var(--primary-button-bg);
--suggestions-focus-color: var(--default-text-color-inverted);
--suggestions-default-opt-bg: fade(@primary-button-bg, 10%);
--suggestions-default-opt-color: var(--default-text-color-light);
--suggestions-hover-bg: fade(@primary-button-bg, 30%);
--suggestions-border-color: var(--base-gray-light);
--suggestions-separation-bg: var(--base-gray-lighter);
--suggestions-failure-message-color: var(--default-text-color-light);
--suggestions-relation-path-bg: var(--base-gray-lighter);
--suggestions-relation-path-focus-bg: var(--base-gray);
--card-border-color: var(--base-gray-light);
--schedule-element-fields-bg: var(--default-input-bg);
--schedule-element-fields-color: var(--base-primary-color);
--schedule-element-fields-border-color: var(--base-gray-light);
--schedule-element-fields-selected-bg: var(--primary-button-bg);
--schedule-element-fields-selected-color: var(--default-text-color-inverted);
--schedule-element-fields-hover-bg: @base-primary-light;
--schedule-element-fields-outline-color: fade(@base-primary-bg, 50%);
--schedule-element-fields-selected-outline-color: fade(#fff, 50%);
--schedule-element-fields-selected-hover-bg: var(--primary-button-hover-bg);
--schedule-element-fields-disabled-color: var(--base-gray);
--schedule-element-fields-disabled-bg: var(--base-gray-lighter);
--schedule-element-fields-disabled-selected-bg: var(--base-gray-light);
--schedule-element-keyboard-note-bg: var(--base-gray-light);
}
};

791
asset/css/vendor/flatpickr.css vendored Normal file
View File

@ -0,0 +1,791 @@
.flatpickr-calendar {
background: transparent;
opacity: 0;
display: none;
text-align: center;
visibility: hidden;
padding: 0;
-webkit-animation: none;
animation: none;
direction: ltr;
border: 0;
font-size: 14px;
line-height: 24px;
border-radius: 5px;
position: absolute;
width: 307.875px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-ms-touch-action: manipulation;
touch-action: manipulation;
background: #fff;
-webkit-box-shadow: 1px 0 0 #e6e6e6, -1px 0 0 #e6e6e6, 0 1px 0 #e6e6e6, 0 -1px 0 #e6e6e6, 0 3px 13px rgba(0,0,0,0.08);
box-shadow: 1px 0 0 #e6e6e6, -1px 0 0 #e6e6e6, 0 1px 0 #e6e6e6, 0 -1px 0 #e6e6e6, 0 3px 13px rgba(0,0,0,0.08);
}
.flatpickr-calendar.open,
.flatpickr-calendar.inline {
opacity: 1;
max-height: 640px;
visibility: visible;
}
.flatpickr-calendar.open {
display: inline-block;
z-index: 99999;
}
.flatpickr-calendar.animate.open {
-webkit-animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1);
animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.flatpickr-calendar.inline {
display: block;
position: relative;
top: 2px;
}
.flatpickr-calendar.static {
position: absolute;
top: calc(100% + 2px);
}
.flatpickr-calendar.static.open {
z-index: 999;
display: block;
}
.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+1) .flatpickr-day.inRange:nth-child(7n+7) {
-webkit-box-shadow: none !important;
box-shadow: none !important;
}
.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+2) .flatpickr-day.inRange:nth-child(7n+1) {
-webkit-box-shadow: -2px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;
box-shadow: -2px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;
}
.flatpickr-calendar .hasWeeks .dayContainer,
.flatpickr-calendar .hasTime .dayContainer {
border-bottom: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.flatpickr-calendar .hasWeeks .dayContainer {
border-left: 0;
}
.flatpickr-calendar.hasTime .flatpickr-time {
height: 40px;
border-top: 1px solid #e6e6e6;
}
.flatpickr-calendar.noCalendar.hasTime .flatpickr-time {
height: auto;
}
.flatpickr-calendar:before,
.flatpickr-calendar:after {
position: absolute;
display: block;
pointer-events: none;
border: solid transparent;
content: '';
height: 0;
width: 0;
left: 22px;
}
.flatpickr-calendar.rightMost:before,
.flatpickr-calendar.arrowRight:before,
.flatpickr-calendar.rightMost:after,
.flatpickr-calendar.arrowRight:after {
left: auto;
right: 22px;
}
.flatpickr-calendar.arrowCenter:before,
.flatpickr-calendar.arrowCenter:after {
left: 50%;
right: 50%;
}
.flatpickr-calendar:before {
border-width: 5px;
margin: 0 -5px;
}
.flatpickr-calendar:after {
border-width: 4px;
margin: 0 -4px;
}
.flatpickr-calendar.arrowTop:before,
.flatpickr-calendar.arrowTop:after {
bottom: 100%;
}
.flatpickr-calendar.arrowTop:before {
border-bottom-color: #e6e6e6;
}
.flatpickr-calendar.arrowTop:after {
border-bottom-color: #fff;
}
.flatpickr-calendar.arrowBottom:before,
.flatpickr-calendar.arrowBottom:after {
top: 100%;
}
.flatpickr-calendar.arrowBottom:before {
border-top-color: #e6e6e6;
}
.flatpickr-calendar.arrowBottom:after {
border-top-color: #fff;
}
.flatpickr-calendar:focus {
outline: 0;
}
.flatpickr-wrapper {
position: relative;
display: inline-block;
}
.flatpickr-months {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.flatpickr-months .flatpickr-month {
background: transparent;
color: rgba(0,0,0,0.9);
fill: rgba(0,0,0,0.9);
height: 34px;
line-height: 1;
text-align: center;
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
overflow: hidden;
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
}
.flatpickr-months .flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month {
text-decoration: none;
cursor: pointer;
position: absolute;
top: 0;
height: 34px;
padding: 10px;
z-index: 3;
color: rgba(0,0,0,0.9);
fill: rgba(0,0,0,0.9);
}
.flatpickr-months .flatpickr-prev-month.flatpickr-disabled,
.flatpickr-months .flatpickr-next-month.flatpickr-disabled {
display: none;
}
.flatpickr-months .flatpickr-prev-month i,
.flatpickr-months .flatpickr-next-month i {
position: relative;
}
.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month.flatpickr-prev-month {
/*
/*rtl:begin:ignore*/
/*
*/
left: 0;
/*
/*rtl:end:ignore*/
/*
*/
}
/*
/*rtl:begin:ignore*/
/*
/*rtl:end:ignore*/
.flatpickr-months .flatpickr-prev-month.flatpickr-next-month,
.flatpickr-months .flatpickr-next-month.flatpickr-next-month {
/*
/*rtl:begin:ignore*/
/*
*/
right: 0;
/*
/*rtl:end:ignore*/
/*
*/
}
/*
/*rtl:begin:ignore*/
/*
/*rtl:end:ignore*/
.flatpickr-months .flatpickr-prev-month:hover,
.flatpickr-months .flatpickr-next-month:hover {
color: #959ea9;
}
.flatpickr-months .flatpickr-prev-month:hover svg,
.flatpickr-months .flatpickr-next-month:hover svg {
fill: #f64747;
}
.flatpickr-months .flatpickr-prev-month svg,
.flatpickr-months .flatpickr-next-month svg {
width: 14px;
height: 14px;
}
.flatpickr-months .flatpickr-prev-month svg path,
.flatpickr-months .flatpickr-next-month svg path {
-webkit-transition: fill 0.1s;
transition: fill 0.1s;
fill: inherit;
}
.numInputWrapper {
position: relative;
height: auto;
}
.numInputWrapper input,
.numInputWrapper span {
display: inline-block;
}
.numInputWrapper input {
width: 100%;
}
.numInputWrapper input::-ms-clear {
display: none;
}
.numInputWrapper input::-webkit-outer-spin-button,
.numInputWrapper input::-webkit-inner-spin-button {
margin: 0;
-webkit-appearance: none;
}
.numInputWrapper span {
position: absolute;
right: 0;
width: 14px;
padding: 0 4px 0 2px;
height: 50%;
line-height: 50%;
opacity: 0;
cursor: pointer;
border: 1px solid rgba(57,57,57,0.15);
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.numInputWrapper span:hover {
background: rgba(0,0,0,0.1);
}
.numInputWrapper span:active {
background: rgba(0,0,0,0.2);
}
.numInputWrapper span:after {
display: block;
content: "";
position: absolute;
}
.numInputWrapper span.arrowUp {
top: 0;
border-bottom: 0;
}
.numInputWrapper span.arrowUp:after {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid rgba(57,57,57,0.6);
top: 26%;
}
.numInputWrapper span.arrowDown {
top: 50%;
}
.numInputWrapper span.arrowDown:after {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid rgba(57,57,57,0.6);
top: 40%;
}
.numInputWrapper span svg {
width: inherit;
height: auto;
}
.numInputWrapper span svg path {
fill: rgba(0,0,0,0.5);
}
.numInputWrapper:hover {
background: rgba(0,0,0,0.05);
}
.numInputWrapper:hover span {
opacity: 1;
}
.flatpickr-current-month {
font-size: 135%;
line-height: inherit;
font-weight: 300;
color: inherit;
position: absolute;
width: 75%;
left: 12.5%;
padding: 7.48px 0 0 0;
line-height: 1;
height: 34px;
display: inline-block;
text-align: center;
-webkit-transform: translate3d(0px, 0px, 0px);
transform: translate3d(0px, 0px, 0px);
}
.flatpickr-current-month span.cur-month {
font-family: inherit;
font-weight: 700;
color: inherit;
display: inline-block;
margin-left: 0.5ch;
padding: 0;
}
.flatpickr-current-month span.cur-month:hover {
background: rgba(0,0,0,0.05);
}
.flatpickr-current-month .numInputWrapper {
width: 6ch;
width: 7ch\0;
display: inline-block;
}
.flatpickr-current-month .numInputWrapper span.arrowUp:after {
border-bottom-color: rgba(0,0,0,0.9);
}
.flatpickr-current-month .numInputWrapper span.arrowDown:after {
border-top-color: rgba(0,0,0,0.9);
}
.flatpickr-current-month input.cur-year {
background: transparent;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: inherit;
cursor: text;
padding: 0 0 0 0.5ch;
margin: 0;
display: inline-block;
font-size: inherit;
font-family: inherit;
font-weight: 300;
line-height: inherit;
height: auto;
border: 0;
border-radius: 0;
vertical-align: initial;
-webkit-appearance: textfield;
-moz-appearance: textfield;
appearance: textfield;
}
.flatpickr-current-month input.cur-year:focus {
outline: 0;
}
.flatpickr-current-month input.cur-year[disabled],
.flatpickr-current-month input.cur-year[disabled]:hover {
font-size: 100%;
color: rgba(0,0,0,0.5);
background: transparent;
pointer-events: none;
}
.flatpickr-current-month .flatpickr-monthDropdown-months {
appearance: menulist;
background: transparent;
border: none;
border-radius: 0;
box-sizing: border-box;
color: inherit;
cursor: pointer;
font-size: inherit;
font-family: inherit;
font-weight: 300;
height: auto;
line-height: inherit;
margin: -1px 0 0 0;
outline: none;
padding: 0 0 0 0.5ch;
position: relative;
vertical-align: initial;
-webkit-box-sizing: border-box;
-webkit-appearance: menulist;
-moz-appearance: menulist;
width: auto;
}
.flatpickr-current-month .flatpickr-monthDropdown-months:focus,
.flatpickr-current-month .flatpickr-monthDropdown-months:active {
outline: none;
}
.flatpickr-current-month .flatpickr-monthDropdown-months:hover {
background: rgba(0,0,0,0.05);
}
.flatpickr-current-month .flatpickr-monthDropdown-months .flatpickr-monthDropdown-month {
background-color: transparent;
outline: none;
padding: 0;
}
.flatpickr-weekdays {
background: transparent;
text-align: center;
overflow: hidden;
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
height: 28px;
}
.flatpickr-weekdays .flatpickr-weekdaycontainer {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
}
span.flatpickr-weekday {
cursor: default;
font-size: 90%;
background: transparent;
color: rgba(0,0,0,0.54);
line-height: 1;
margin: 0;
text-align: center;
display: block;
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
font-weight: bolder;
}
.dayContainer,
.flatpickr-weeks {
padding: 1px 0 0 0;
}
.flatpickr-days {
position: relative;
overflow: hidden;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: start;
-webkit-align-items: flex-start;
-ms-flex-align: start;
align-items: flex-start;
width: 307.875px;
}
.flatpickr-days:focus {
outline: 0;
}
.dayContainer {
padding: 0;
outline: 0;
text-align: left;
width: 307.875px;
min-width: 307.875px;
max-width: 307.875px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
display: inline-block;
display: -ms-flexbox;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
-ms-flex-wrap: wrap;
-ms-flex-pack: justify;
-webkit-justify-content: space-around;
justify-content: space-around;
-webkit-transform: translate3d(0px, 0px, 0px);
transform: translate3d(0px, 0px, 0px);
opacity: 1;
}
.dayContainer + .dayContainer {
-webkit-box-shadow: -1px 0 0 #e6e6e6;
box-shadow: -1px 0 0 #e6e6e6;
}
.flatpickr-day {
background: none;
border: 1px solid transparent;
border-radius: 150px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: #393939;
cursor: pointer;
font-weight: 400;
width: 14.2857143%;
-webkit-flex-basis: 14.2857143%;
-ms-flex-preferred-size: 14.2857143%;
flex-basis: 14.2857143%;
max-width: 39px;
height: 39px;
line-height: 39px;
margin: 0;
display: inline-block;
position: relative;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
text-align: center;
}
.flatpickr-day.inRange,
.flatpickr-day.prevMonthDay.inRange,
.flatpickr-day.nextMonthDay.inRange,
.flatpickr-day.today.inRange,
.flatpickr-day.prevMonthDay.today.inRange,
.flatpickr-day.nextMonthDay.today.inRange,
.flatpickr-day:hover,
.flatpickr-day.prevMonthDay:hover,
.flatpickr-day.nextMonthDay:hover,
.flatpickr-day:focus,
.flatpickr-day.prevMonthDay:focus,
.flatpickr-day.nextMonthDay:focus {
cursor: pointer;
outline: 0;
background: #e6e6e6;
border-color: #e6e6e6;
}
.flatpickr-day.today {
border-color: #959ea9;
}
.flatpickr-day.today:hover,
.flatpickr-day.today:focus {
border-color: #959ea9;
background: #959ea9;
color: #fff;
}
.flatpickr-day.selected,
.flatpickr-day.startRange,
.flatpickr-day.endRange,
.flatpickr-day.selected.inRange,
.flatpickr-day.startRange.inRange,
.flatpickr-day.endRange.inRange,
.flatpickr-day.selected:focus,
.flatpickr-day.startRange:focus,
.flatpickr-day.endRange:focus,
.flatpickr-day.selected:hover,
.flatpickr-day.startRange:hover,
.flatpickr-day.endRange:hover,
.flatpickr-day.selected.prevMonthDay,
.flatpickr-day.startRange.prevMonthDay,
.flatpickr-day.endRange.prevMonthDay,
.flatpickr-day.selected.nextMonthDay,
.flatpickr-day.startRange.nextMonthDay,
.flatpickr-day.endRange.nextMonthDay {
background: #569ff7;
-webkit-box-shadow: none;
box-shadow: none;
color: #fff;
border-color: #569ff7;
}
.flatpickr-day.selected.startRange,
.flatpickr-day.startRange.startRange,
.flatpickr-day.endRange.startRange {
border-radius: 50px 0 0 50px;
}
.flatpickr-day.selected.endRange,
.flatpickr-day.startRange.endRange,
.flatpickr-day.endRange.endRange {
border-radius: 0 50px 50px 0;
}
.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)),
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)),
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)) {
-webkit-box-shadow: -10px 0 0 #569ff7;
box-shadow: -10px 0 0 #569ff7;
}
.flatpickr-day.selected.startRange.endRange,
.flatpickr-day.startRange.startRange.endRange,
.flatpickr-day.endRange.startRange.endRange {
border-radius: 50px;
}
.flatpickr-day.inRange {
border-radius: 0;
-webkit-box-shadow: -5px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;
box-shadow: -5px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;
}
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover,
.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay,
.flatpickr-day.notAllowed,
.flatpickr-day.notAllowed.prevMonthDay,
.flatpickr-day.notAllowed.nextMonthDay {
color: rgba(57,57,57,0.3);
background: transparent;
border-color: transparent;
cursor: default;
}
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover {
cursor: not-allowed;
color: rgba(57,57,57,0.1);
}
.flatpickr-day.week.selected {
border-radius: 0;
-webkit-box-shadow: -5px 0 0 #569ff7, 5px 0 0 #569ff7;
box-shadow: -5px 0 0 #569ff7, 5px 0 0 #569ff7;
}
.flatpickr-day.hidden {
visibility: hidden;
}
.rangeMode .flatpickr-day {
margin-top: 1px;
}
.flatpickr-weekwrapper {
float: left;
}
.flatpickr-weekwrapper .flatpickr-weeks {
padding: 0 12px;
-webkit-box-shadow: 1px 0 0 #e6e6e6;
box-shadow: 1px 0 0 #e6e6e6;
}
.flatpickr-weekwrapper .flatpickr-weekday {
float: none;
width: 100%;
line-height: 28px;
}
.flatpickr-weekwrapper span.flatpickr-day,
.flatpickr-weekwrapper span.flatpickr-day:hover {
display: block;
width: 100%;
max-width: none;
color: rgba(57,57,57,0.3);
background: transparent;
cursor: default;
border: none;
}
.flatpickr-innerContainer {
display: block;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
}
.flatpickr-rContainer {
display: inline-block;
padding: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.flatpickr-time {
text-align: center;
outline: 0;
display: block;
height: 0;
line-height: 40px;
max-height: 40px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.flatpickr-time:after {
content: "";
display: table;
clear: both;
}
.flatpickr-time .numInputWrapper {
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
width: 40%;
height: 40px;
float: left;
}
.flatpickr-time .numInputWrapper span.arrowUp:after {
border-bottom-color: #393939;
}
.flatpickr-time .numInputWrapper span.arrowDown:after {
border-top-color: #393939;
}
.flatpickr-time.hasSeconds .numInputWrapper {
width: 26%;
}
.flatpickr-time.time24hr .numInputWrapper {
width: 49%;
}
.flatpickr-time input {
background: transparent;
-webkit-box-shadow: none;
box-shadow: none;
border: 0;
border-radius: 0;
text-align: center;
margin: 0;
padding: 0;
height: inherit;
line-height: inherit;
color: #393939;
font-size: 14px;
position: relative;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-webkit-appearance: textfield;
-moz-appearance: textfield;
appearance: textfield;
}
.flatpickr-time input.flatpickr-hour {
font-weight: bold;
}
.flatpickr-time input.flatpickr-minute,
.flatpickr-time input.flatpickr-second {
font-weight: 400;
}
.flatpickr-time input:focus {
outline: 0;
border: 0;
}
.flatpickr-time .flatpickr-time-separator,
.flatpickr-time .flatpickr-am-pm {
height: inherit;
float: left;
line-height: inherit;
color: #393939;
font-weight: bold;
width: 2%;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-align-self: center;
-ms-flex-item-align: center;
align-self: center;
}
.flatpickr-time .flatpickr-am-pm {
outline: 0;
width: 18%;
cursor: pointer;
text-align: center;
font-weight: 400;
}
.flatpickr-time input:hover,
.flatpickr-time .flatpickr-am-pm:hover,
.flatpickr-time input:focus,
.flatpickr-time .flatpickr-am-pm:focus {
background: #eee;
}
.flatpickr-input[readonly] {
cursor: pointer;
}
@-webkit-keyframes fpFadeInDown {
from {
opacity: 0;
-webkit-transform: translate3d(0, -20px, 0);
transform: translate3d(0, -20px, 0);
}
to {
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}
@keyframes fpFadeInDown {
from {
opacity: 0;
-webkit-transform: translate3d(0, -20px, 0);
transform: translate3d(0, -20px, 0);
}
to {
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}

327
asset/css/vendor/flatpickr.vars.less vendored Normal file
View File

@ -0,0 +1,327 @@
/**
* This file's only purpose is to make the flatpickr themeable. DO NOT add ANY custom style here!
* Also, DO NOT re-arrange the CSS blocks to make them more LESS like. They're based off of the
* pre-compiled flatpickr.css file and so can easily identified when updating to a new version.
*
* Non-standard LESS variables were added to allow usage of CSS variables. All of them hold a
* value calculated by LESS functions. If not temporarily stored in another LESS variable,
* they wouldn't be available to CSS variable usage.
*
* Latest state from version: v4.6.9
*/
@fp-calendarBackground: #ffffff;
@fp-calendarBorderColor: #e6e6e6;
@fp-arrowColor: fadeout(@fp-dayForeground, 40%); // Non-standard variable
@fp-arrow_hover_color: #f64747;
@fp-monthForeground: fadeout(black, 10%);
@fp-monthBackground: transparent;
@fp-weekdaysBackground: transparent;
@fp-weekdaysForeground: fadeout(black, 46%);
@fp-weekNumberForeground: fadeout(@fp-dayForeground, 70%); // Non-standard variable
@fp-dayForeground: #393939;
@fp-dayHoverBackground: #e6e6e6;
@fp-disabledDayForeground: fadeout(@fp-dayForeground, 90%); // Non-standard variable
@fp-outsideRangeDayForeground: @fp-weekNumberForeground; // Non-standard variable
@fp-selectedDayBackground: #569FF7;
@fp-todayColor: #959ea9;
@fp-timeHoverBg: lighten(@fp-dayHoverBackground, 3); // Non-standard variable
@fp-invertedBg: black;
@fp-hoverInvertedBg: fadeout(@fp-invertedBg, 95%); // Non-standard variable
@fp-numChooserSvgFillColor: fadeout(@fp-monthForeground, 50%); // Non-standard variable
@fp-hoverNumChooserBg: fadeout(@fp-invertedBg, 90%); // Non-standard variable
@fp-numChooserBorderColor: fadeout(@fp-dayForeground, 85%); // Non-standard variable
.icinga-datetime-picker {
&.flatpickr-calendar {
background: @fp-calendarBackground;
background: var(--fp-calendarBackground, @fp-calendarBackground);
box-shadow: 1px 0 0 @fp-calendarBorderColor,
-1px 0 0 @fp-calendarBorderColor,
0 1px 0 @fp-calendarBorderColor,
0 -1px 0 @fp-calendarBorderColor,
0 3px 13px fadeout(black, 92%);
box-shadow: 1px 0 0 var(--fp-calendarBorderColor, @fp-calendarBorderColor),
-1px 0 0 var(--fp-calendarBorderColor, @fp-calendarBorderColor),
0 1px 0 var(--fp-calendarBorderColor, @fp-calendarBorderColor),
0 -1px 0 var(--fp-calendarBorderColor, @fp-calendarBorderColor),
0 3px 13px fadeout(black, 92%);
}
&.flatpickr-calendar.arrowTop:before {
border-bottom-color: @fp-calendarBorderColor;
border-bottom-color: var(--fp-calendarBorderColor, @fp-calendarBorderColor);
}
&.flatpickr-calendar.arrowTop:after {
border-bottom-color: @fp-calendarBackground;
border-bottom-color: var(--fp-calendarBackground, @fp-calendarBackground);
}
&.flatpickr-calendar.arrowBottom:before {
border-top-color: @fp-calendarBorderColor;
border-top-color: var(--fp-calendarBorderColor, @fp-calendarBorderColor);
}
&.flatpickr-calendar.arrowBottom:after {
border-top-color: @fp-calendarBackground;
border-top-color: var(--fp-calendarBackground, @fp-calendarBackground);
}
&.flatpickr-calendar.hasTime .flatpickr-time {
border-top-color: @fp-calendarBorderColor;
border-top-color: var(--fp-calendarBorderColor, @fp-calendarBorderColor);
}
.dayContainer + .dayContainer {
-webkit-box-shadow: -1px 0 0 @fp-calendarBorderColor;
-webkit-box-shadow: -1px 0 0 var(--fp-calendarBorderColor, @fp-calendarBorderColor);
box-shadow: -1px 0 0 @fp-calendarBorderColor;
box-shadow: -1px 0 0 var(--fp-calendarBorderColor, @fp-calendarBorderColor);
}
.flatpickr-day {
color: @fp-dayForeground;
color: var(--fp-dayForeground, @fp-dayForeground);
}
.flatpickr-day.today {
border-color: @fp-todayColor;
border-color: var(--fp-todayColor, @fp-todayColor);
}
.flatpickr-day.today:hover,
.flatpickr-day.today:focus {
border-color: @fp-todayColor;
border-color: var(--fp-todayColor, @fp-todayColor);
background: @fp-todayColor;
background: var(--fp-todayColor, @fp-todayColor);
color: @fp-calendarBackground;
color: var(--fp-calendarBackground, @fp-calendarBackground);
}
.flatpickr-day.selected,
.flatpickr-day.startRange,
.flatpickr-day.endRange,
.flatpickr-day.selected.inRange,
.flatpickr-day.startRange.inRange,
.flatpickr-day.endRange.inRange,
.flatpickr-day.selected:focus,
.flatpickr-day.startRange:focus,
.flatpickr-day.endRange:focus,
.flatpickr-day.selected:hover,
.flatpickr-day.startRange:hover,
.flatpickr-day.endRange:hover,
.flatpickr-day.selected.prevMonthDay,
.flatpickr-day.startRange.prevMonthDay,
.flatpickr-day.endRange.prevMonthDay,
.flatpickr-day.selected.nextMonthDay,
.flatpickr-day.startRange.nextMonthDay,
.flatpickr-day.endRange.nextMonthDay {
color: @fp-calendarBackground;
color: var(--fp-calendarBackground, @fp-calendarBackground);
}
.flatpickr-day.inRange,
.flatpickr-day.prevMonthDay.inRange,
.flatpickr-day.nextMonthDay.inRange,
.flatpickr-day.today.inRange,
.flatpickr-day.prevMonthDay.today.inRange,
.flatpickr-day.nextMonthDay.today.inRange,
.flatpickr-day:hover,
.flatpickr-day.prevMonthDay:hover,
.flatpickr-day.nextMonthDay:hover,
.flatpickr-day:focus,
.flatpickr-day.nextMonthDay:focus {
background: @fp-dayHoverBackground;
background: var(--fp-dayHoverBackground, @fp-dayHoverBackground);
border-color: @fp-dayHoverBackground;
border-color: var(--fp-dayHoverBackground, @fp-dayHoverBackground);
}
.flatpickr-day.inRange {
-webkit-box-shadow: -5px 0 0 @fp-dayHoverBackground, 5px 0 0 @fp-dayHoverBackground;
-webkit-box-shadow: -5px 0 0 var(--fp-dayHoverBackground, @fp-dayHoverBackground),
5px 0 0 var(--fp-dayHoverBackground, @fp-dayHoverBackground);
box-shadow: -5px 0 0 @fp-dayHoverBackground, 5px 0 0 @fp-dayHoverBackground;
box-shadow: -5px 0 0 var(--fp-dayHoverBackground, @fp-dayHoverBackground),
5px 0 0 var(--fp-dayHoverBackground, @fp-dayHoverBackground);
}
.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay,
.flatpickr-day.notAllowed,
.flatpickr-day.notAllowed.prevMonthDay,
.flatpickr-day.notAllowed.nextMonthDay {
color: @fp-outsideRangeDayForeground;
color: var(--fp-outsideRangeDayForeground, @fp-outsideRangeDayForeground);
}
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover {
color: @fp-disabledDayForeground;
color: var(--fp-disabledDayForeground, @fp-disabledDayForeground);
}
.flatpickr-day.selected,
.flatpickr-day.startRange,
.flatpickr-day.endRange,
.flatpickr-day.selected.inRange,
.flatpickr-day.startRange.inRange,
.flatpickr-day.endRange.inRange,
.flatpickr-day.selected:focus,
.flatpickr-day.startRange:focus,
.flatpickr-day.endRange:focus,
.flatpickr-day.selected:hover,
.flatpickr-day.startRange:hover,
.flatpickr-day.endRange:hover,
.flatpickr-day.selected.prevMonthDay,
.flatpickr-day.startRange.prevMonthDay,
.flatpickr-day.endRange.prevMonthDay,
.flatpickr-day.selected.nextMonthDay,
.flatpickr-day.startRange.nextMonthDay,
.flatpickr-day.endRange.nextMonthDay {
background: @fp-selectedDayBackground;
background: var(--fp-selectedDayBackground, @fp-selectedDayBackground);
border-color: @fp-selectedDayBackground;
border-color: var(--fp-selectedDayBackground, @fp-selectedDayBackground);
}
.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)),
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)),
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)) {
-webkit-box-shadow: -10px 0 0 @fp-selectedDayBackground;
-webkit-box-shadow: -10px 0 0 var(--fp-selectedDayBackground, @fp-selectedDayBackground);
box-shadow: -10px 0 0 @fp-selectedDayBackground;
box-shadow: -10px 0 0 var(--fp-selectedDayBackground, @fp-selectedDayBackground);
}
.flatpickr-day.week.selected {
-webkit-box-shadow: -5px 0 0 @fp-selectedDayBackground, 5px 0 0 @fp-selectedDayBackground;
-webkit-box-shadow: -5px 0 0 var(--fp-selectedDayBackground, @fp-selectedDayBackground),
5px 0 0 var(--fp-selectedDayBackground, @fp-selectedDayBackground);
box-shadow: -5px 0 0 @fp-selectedDayBackground, 5px 0 0 @fp-selectedDayBackground;
box-shadow: -5px 0 0 var(--fp-selectedDayBackground, @fp-selectedDayBackground),
5px 0 0 var(--fp-selectedDayBackground, @fp-selectedDayBackground);
}
.flatpickr-weekwrapper .flatpickr-weeks {
-webkit-box-shadow: 1px 0 0 @fp-calendarBorderColor;
-webkit-box-shadow: 1px 0 0 var(--fp-calendarBorderColor, @fp-calendarBorderColor);
box-shadow: 1px 0 0 @fp-calendarBorderColor;
box-shadow: 1px 0 0 var(--fp-calendarBorderColor, @fp-calendarBorderColor);
}
.flatpickr-weekwrapper span.flatpickr-day,
.flatpickr-weekwrapper span.flatpickr-day:hover {
color: @fp-weekNumberForeground;
color: var(--fp-weekNumberForeground, @fp-weekNumberForeground);
}
.flatpickr-weekdays {
background: @fp-weekdaysBackground;
background: var(--fp-weekdaysBackground, @fp-weekdaysBackground);
}
span.flatpickr-weekday {
background: @fp-monthBackground;
background: var(--fp-monthBackground, @fp-monthBackground);
color: @fp-weekdaysForeground;
color: var(--fp-weekdaysForeground, @fp-weekdaysForeground);
}
.flatpickr-months .flatpickr-month {
background: @fp-monthBackground;
background: var(--fp-monthBackground, @fp-monthBackground);
color: @fp-monthForeground;
color: var(--fp-monthForeground, @fp-monthForeground);
fill: @fp-monthForeground;
fill: var(--fp-monthForeground, @fp-monthForeground);
}
.flatpickr-months .flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month {
color: @fp-monthForeground;
color: var(--fp-monthForeground, @fp-monthForeground);
fill: @fp-monthForeground;
fill: var(--fp-monthForeground, @fp-monthForeground);
}
.flatpickr-months .flatpickr-prev-month:hover,
.flatpickr-months .flatpickr-next-month:hover {
color: @fp-todayColor;
color: var(--fp-todayColor, @fp-todayColor);
}
.flatpickr-months .flatpickr-prev-month:hover svg,
.flatpickr-months .flatpickr-next-month:hover svg {
fill: @fp-arrow_hover_color;
fill: var(--fp-arrow_hover_color, @fp-arrow_hover_color);
}
.flatpickr-current-month .flatpickr-monthDropdown-months {
background: @fp-monthBackground;
background: var(--fp-monthBackground, @fp-monthBackground);
}
.flatpickr-current-month .flatpickr-monthDropdown-months .flatpickr-monthDropdown-month {
background-color: @fp-monthBackground;
background-color: var(--fp-monthBackground, @fp-monthBackground);
}
.flatpickr-current-month .numInputWrapper span.arrowUp:after {
border-bottom-color: @fp-monthForeground;
border-bottom-color: var(--fp-monthForeground, @fp-monthForeground);
}
.flatpickr-current-month .numInputWrapper span.arrowDown:after {
border-top-color: @fp-monthForeground;
border-top-color: var(--fp-monthForeground, @fp-monthForeground);
}
.numInputWrapper span {
border-color: @fp-numChooserBorderColor;
border-color: var(--fp-numChooserBorderColor, @fp-numChooserBorderColor);
}
.numInputWrapper span:hover {
background: @fp-hoverNumChooserBg;
background: var(--fp-hoverNumChooserBg, @fp-hoverNumChooserBg);
}
.numInputWrapper span:active {
background: @fp-hoverNumChooserBg;
background: var(--fp-hoverNumChooserBg, @fp-hoverNumChooserBg);
}
.numInputWrapper span svg path {
fill: @fp-numChooserSvgFillColor;
fill: var(--fp-numChooserSvgFillColor, @fp-numChooserSvgFillColor);
}
.numInputWrapper span.arrowUp:after {
border-bottom-color: @fp-arrowColor;
border-bottom-color: var(--fp-arrowColor, @fp-arrowColor);
}
.numInputWrapper span.arrowDown:after {
border-top-color: @fp-arrowColor;
border-top-color: var(--fp-arrowColor, @fp-arrowColor);
}
.numInputWrapper:hover {
background: @fp-hoverInvertedBg;
background: var(--fp-hoverInvertedBg, @fp-hoverInvertedBg);
}
.flatpickr-current-month span.cur-month:hover {
background: @fp-hoverInvertedBg;
background: var(--fp-hoverInvertedBg, @fp-hoverInvertedBg);
}
.flatpickr-current-month .flatpickr-monthDropdown-months:hover {
background: @fp-hoverInvertedBg;
background: var(--fp-hoverInvertedBg, @fp-hoverInvertedBg);
}
.flatpickr-time input:hover,
.flatpickr-time .flatpickr-am-pm:hover,
.flatpickr-time input:focus,
.flatpickr-time .flatpickr-am-pm:focus {
background: @fp-timeHoverBg;
background: var(--fp-timeHoverBg, @fp-timeHoverBg);
}
.flatpickr-time .numInputWrapper span.arrowUp:after {
border-bottom-color: @fp-dayForeground;
border-bottom-color: var(--fp-dayForeground, @fp-dayForeground);
}
.flatpickr-time .numInputWrapper span.arrowDown:after {
border-top-color: @fp-dayForeground;
border-top-color: var(--fp-dayForeground, @fp-dayForeground);
}
.flatpickr-time input {
color: @fp-dayForeground;
color: var(--fp-dayForeground, @fp-dayForeground);
}
.flatpickr-time .flatpickr-time-separator,
.flatpickr-time .flatpickr-am-pm {
color: @fp-dayForeground;
color: var(--fp-dayForeground, @fp-dayForeground);
}
}

View File

@ -0,0 +1,17 @@
.vertical-key-value {
display: inline-block;
line-height: .75;
text-align: center;
vertical-align: middle;
.key {
font-size: 10/12em;
color: var(--default-text-color-light, @default-text-color-light);
}
.value {
color: var(--default-text-color, @default-text-color);
font-size: 1.5em;
font-weight: bold;
}
}

161
asset/js/notjQuery.js Normal file
View File

@ -0,0 +1,161 @@
define(function () {
"use strict";
class notjQuery {
/**
* Create a new notjQuery object
*
* @param {Element} element
*/
constructor(element) {
if (! element) {
throw new Error("Can't create a notjQuery object for `" + element + "`");
}
this.element = element;
}
/**
* Add an event listener to the element
*
* @param {string} type
* @param {string} selector
* @param {function} handler
* @param {object} context
*/
on(type, selector, handler, context = null) {
if (typeof selector === 'function') {
context = handler;
handler = selector;
selector = null;
}
if (selector === null) {
this.element.addEventListener(type, e => {
if (type === 'focusin' && e.target.receivesCustomFocus) {
// Ignore native focus event if a custom one follows
if (e instanceof FocusEvent) {
delete e.target.receivesCustomFocus;
e.stopImmediatePropagation();
return;
}
}
if (context === null) {
handler.apply(e.currentTarget, [e]);
} else {
handler.apply(context, [e]);
}
});
} else {
this.element.addEventListener(type, e => {
if (type === 'focusin' && e.target.receivesCustomFocus) {
// Ignore native focus event if a custom one follows
if (e instanceof FocusEvent) {
delete e.target.receivesCustomFocus;
e.stopImmediatePropagation();
return;
}
}
Object.defineProperty(e, 'currentTarget', { value: e.currentTarget, writable: true });
let currentParent = e.currentTarget.parentNode;
for (let target = e.target; target && target !== currentParent; target = target.parentNode) {
if (target.matches(selector)) {
e.currentTarget = target;
if (context === null) {
handler.apply(target, [e]);
} else {
handler.apply(context, [e]);
}
break;
}
}
}, false);
}
}
/**
* Trigger a custom event on the element, asynchronously
*
* The event will bubble and is not cancelable.
*
* @param {string} type
* @param {{}} detail
*/
trigger(type, detail = null) {
setTimeout(() => {
this.element.dispatchEvent(new CustomEvent(type, {
cancelable: true, // TODO: this should depend on whether it's a native or custom event
bubbles: true,
detail: detail
}));
}, 0);
}
/**
* Focus the element
*
* Any other option than `preventScroll` is used as `event.detail`.
*
* @param {{}} options
*/
focus(options = {}) {
let { preventScroll = false, ...data } = options;
const hasData = Object.keys(data).length > 0;
if (hasData) {
this.element.receivesCustomFocus = true;
}
// Put separately on the event loop because focus() forces layout.
setTimeout(() => this.element.focus({ preventScroll: preventScroll }), 0);
if (hasData) {
this.trigger('focusin', data);
}
}
/**
* Render the element string as DOM Element
*
* @param {string} html
* @return {Element}
*/
static render(html) {
if (typeof html !== 'string') {
throw new Error("Can\'t render `" + html + "`");
}
let template = document.createElement('template');
template.innerHTML = html;
return template.content.firstChild;
}
}
/**
* Return a notjQuery object for the given element
*
* @param {Element} element
* @return {notjQuery}
*/
let factory = function (element) {
return new notjQuery(element);
}
// Define the static methods on the factory
for (let name of Object.getOwnPropertyNames(notjQuery)) {
if (['length', 'prototype', 'name'].includes(name)) {
continue;
}
Object.defineProperty(factory, name, {
value: notjQuery[name]
});
}
return factory;
});

3721
asset/js/vendor/Sortable.js vendored Normal file

File diff suppressed because it is too large Load Diff

2
asset/js/vendor/Sortable.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2705
asset/js/vendor/flatpickr.js vendored Normal file

File diff suppressed because it is too large Load Diff

2
asset/js/vendor/flatpickr.min.js vendored Normal file

File diff suppressed because one or more lines are too long

62
asset/js/vendor/flatpickr/l10n/ar.js vendored Normal file
View File

@ -0,0 +1,62 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.icinga ? define(["exports"], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ar = {}));
}(this, (function (exports) { 'use strict';
var fp = typeof window !== "undefined" && window.flatpickr !== undefined
? window.flatpickr
: {
l10ns: {},
};
var Arabic = {
weekdays: {
shorthand: ["أحد", "اثنين", "ثلاثاء", "أربعاء", "خميس", "جمعة", "سبت"],
longhand: [
"الأحد",
"الاثنين",
"الثلاثاء",
"الأربعاء",
"الخميس",
"الجمعة",
"السبت",
],
},
months: {
shorthand: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"],
longhand: [
"يناير",
"فبراير",
"مارس",
"أبريل",
"مايو",
"يونيو",
"يوليو",
"أغسطس",
"سبتمبر",
"أكتوبر",
"نوفمبر",
"ديسمبر",
],
},
firstDayOfWeek: 6,
rangeSeparator: " إلى ",
weekAbbreviation: "Wk",
scrollTitle: "قم بالتمرير للزيادة",
toggleTitle: "اضغط للتبديل",
amPM: ["ص", "م"],
yearAriaLabel: "سنة",
monthAriaLabel: "شهر",
hourAriaLabel: "ساعة",
minuteAriaLabel: "دقيقة",
time_24hr: false,
};
fp.l10ns.ar = Arabic;
var ar = fp.l10ns;
exports.Arabic = Arabic;
exports.default = ar;
Object.defineProperty(exports, '__esModule', { value: true });
})));

70
asset/js/vendor/flatpickr/l10n/de.js vendored Normal file
View File

@ -0,0 +1,70 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.icinga ? define(["exports"], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.de = {}));
}(this, (function (exports) { 'use strict';
var fp = typeof window !== "undefined" && window.flatpickr !== undefined
? window.flatpickr
: {
l10ns: {},
};
var German = {
weekdays: {
shorthand: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"],
longhand: [
"Sonntag",
"Montag",
"Dienstag",
"Mittwoch",
"Donnerstag",
"Freitag",
"Samstag",
],
},
months: {
shorthand: [
"Jan",
"Feb",
"Mär",
"Apr",
"Mai",
"Jun",
"Jul",
"Aug",
"Sep",
"Okt",
"Nov",
"Dez",
],
longhand: [
"Januar",
"Februar",
"März",
"April",
"Mai",
"Juni",
"Juli",
"August",
"September",
"Oktober",
"November",
"Dezember",
],
},
firstDayOfWeek: 1,
weekAbbreviation: "KW",
rangeSeparator: " bis ",
scrollTitle: "Zum Ändern scrollen",
toggleTitle: "Zum Umschalten klicken",
time_24hr: true,
};
fp.l10ns.de = German;
var de = fp.l10ns;
exports.German = German;
exports.default = de;
Object.defineProperty(exports, '__esModule', { value: true });
})));

70
asset/js/vendor/flatpickr/l10n/es.js vendored Normal file
View File

@ -0,0 +1,70 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.icinga ? define(["exports"], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.es = {}));
}(this, (function (exports) { 'use strict';
var fp = typeof window !== "undefined" && window.flatpickr !== undefined
? window.flatpickr
: {
l10ns: {},
};
var Spanish = {
weekdays: {
shorthand: ["Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb"],
longhand: [
"Domingo",
"Lunes",
"Martes",
"Miércoles",
"Jueves",
"Viernes",
"Sábado",
],
},
months: {
shorthand: [
"Ene",
"Feb",
"Mar",
"Abr",
"May",
"Jun",
"Jul",
"Ago",
"Sep",
"Oct",
"Nov",
"Dic",
],
longhand: [
"Enero",
"Febrero",
"Marzo",
"Abril",
"Mayo",
"Junio",
"Julio",
"Agosto",
"Septiembre",
"Octubre",
"Noviembre",
"Diciembre",
],
},
ordinal: function () {
return "º";
},
firstDayOfWeek: 1,
rangeSeparator: " a ",
time_24hr: true,
};
fp.l10ns.es = Spanish;
var es = fp.l10ns;
exports.Spanish = Spanish;
exports.default = es;
Object.defineProperty(exports, '__esModule', { value: true });
})));

69
asset/js/vendor/flatpickr/l10n/fi.js vendored Normal file
View File

@ -0,0 +1,69 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.icinga ? define(["exports"], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.fi = {}));
}(this, (function (exports) { 'use strict';
var fp = typeof window !== "undefined" && window.flatpickr !== undefined
? window.flatpickr
: {
l10ns: {},
};
var Finnish = {
firstDayOfWeek: 1,
weekdays: {
shorthand: ["su", "ma", "ti", "ke", "to", "pe", "la"],
longhand: [
"sunnuntai",
"maanantai",
"tiistai",
"keskiviikko",
"torstai",
"perjantai",
"lauantai",
],
},
months: {
shorthand: [
"tammi",
"helmi",
"maalis",
"huhti",
"touko",
"kesä",
"heinä",
"elo",
"syys",
"loka",
"marras",
"joulu",
],
longhand: [
"tammikuu",
"helmikuu",
"maaliskuu",
"huhtikuu",
"toukokuu",
"kesäkuu",
"heinäkuu",
"elokuu",
"syyskuu",
"lokakuu",
"marraskuu",
"joulukuu",
],
},
ordinal: function () {
return ".";
},
time_24hr: true,
};
fp.l10ns.fi = Finnish;
var fi = fp.l10ns;
exports.Finnish = Finnish;
exports.default = fi;
Object.defineProperty(exports, '__esModule', { value: true });
})));

75
asset/js/vendor/flatpickr/l10n/fr.js vendored Normal file
View File

@ -0,0 +1,75 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.icinga ? define(["exports"], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.fr = {}));
}(this, (function (exports) { 'use strict';
var fp = typeof window !== "undefined" && window.flatpickr !== undefined
? window.flatpickr
: {
l10ns: {},
};
var French = {
firstDayOfWeek: 1,
weekdays: {
shorthand: ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"],
longhand: [
"dimanche",
"lundi",
"mardi",
"mercredi",
"jeudi",
"vendredi",
"samedi",
],
},
months: {
shorthand: [
"janv",
"févr",
"mars",
"avr",
"mai",
"juin",
"juil",
"août",
"sept",
"oct",
"nov",
"déc",
],
longhand: [
"janvier",
"février",
"mars",
"avril",
"mai",
"juin",
"juillet",
"août",
"septembre",
"octobre",
"novembre",
"décembre",
],
},
ordinal: function (nth) {
if (nth > 1)
return "";
return "er";
},
rangeSeparator: " au ",
weekAbbreviation: "Sem",
scrollTitle: "Défiler pour augmenter la valeur",
toggleTitle: "Cliquer pour basculer",
time_24hr: true,
};
fp.l10ns.fr = French;
var fr = fp.l10ns;
exports.French = French;
exports.default = fr;
Object.defineProperty(exports, '__esModule', { value: true });
})));

71
asset/js/vendor/flatpickr/l10n/it.js vendored Normal file
View File

@ -0,0 +1,71 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.icinga ? define(["exports"], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.it = {}));
}(this, (function (exports) { 'use strict';
var fp = typeof window !== "undefined" && window.flatpickr !== undefined
? window.flatpickr
: {
l10ns: {},
};
var Italian = {
weekdays: {
shorthand: ["Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab"],
longhand: [
"Domenica",
"Lunedì",
"Martedì",
"Mercoledì",
"Giovedì",
"Venerdì",
"Sabato",
],
},
months: {
shorthand: [
"Gen",
"Feb",
"Mar",
"Apr",
"Mag",
"Giu",
"Lug",
"Ago",
"Set",
"Ott",
"Nov",
"Dic",
],
longhand: [
"Gennaio",
"Febbraio",
"Marzo",
"Aprile",
"Maggio",
"Giugno",
"Luglio",
"Agosto",
"Settembre",
"Ottobre",
"Novembre",
"Dicembre",
],
},
firstDayOfWeek: 1,
ordinal: function () { return "°"; },
rangeSeparator: " al ",
weekAbbreviation: "Se",
scrollTitle: "Scrolla per aumentare",
toggleTitle: "Clicca per cambiare",
time_24hr: true,
};
fp.l10ns.it = Italian;
var it = fp.l10ns;
exports.Italian = Italian;
exports.default = it;
Object.defineProperty(exports, '__esModule', { value: true });
})));

71
asset/js/vendor/flatpickr/l10n/ja.js vendored Normal file
View File

@ -0,0 +1,71 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.icinga ? define(["exports"], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ja = {}));
}(this, (function (exports) { 'use strict';
var fp = typeof window !== "undefined" && window.flatpickr !== undefined
? window.flatpickr
: {
l10ns: {},
};
var Japanese = {
weekdays: {
shorthand: ["日", "月", "火", "水", "木", "金", "土"],
longhand: [
"日曜日",
"月曜日",
"火曜日",
"水曜日",
"木曜日",
"金曜日",
"土曜日",
],
},
months: {
shorthand: [
"1月",
"2月",
"3月",
"4月",
"5月",
"6月",
"7月",
"8月",
"9月",
"10月",
"11月",
"12月",
],
longhand: [
"1月",
"2月",
"3月",
"4月",
"5月",
"6月",
"7月",
"8月",
"9月",
"10月",
"11月",
"12月",
],
},
time_24hr: true,
rangeSeparator: " から ",
monthAriaLabel: "月",
amPM: ["午前", "午後"],
yearAriaLabel: "年",
hourAriaLabel: "時間",
minuteAriaLabel: "分",
};
fp.l10ns.ja = Japanese;
var ja = fp.l10ns;
exports.Japanese = Japanese;
exports.default = ja;
Object.defineProperty(exports, '__esModule', { value: true });
})));

66
asset/js/vendor/flatpickr/l10n/pt.js vendored Normal file
View File

@ -0,0 +1,66 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.icinga ? define(["exports"], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.pt = {}));
}(this, (function (exports) { 'use strict';
var fp = typeof window !== "undefined" && window.flatpickr !== undefined
? window.flatpickr
: {
l10ns: {},
};
var Portuguese = {
weekdays: {
shorthand: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"],
longhand: [
"Domingo",
"Segunda-feira",
"Terça-feira",
"Quarta-feira",
"Quinta-feira",
"Sexta-feira",
"Sábado",
],
},
months: {
shorthand: [
"Jan",
"Fev",
"Mar",
"Abr",
"Mai",
"Jun",
"Jul",
"Ago",
"Set",
"Out",
"Nov",
"Dez",
],
longhand: [
"Janeiro",
"Fevereiro",
"Março",
"Abril",
"Maio",
"Junho",
"Julho",
"Agosto",
"Setembro",
"Outubro",
"Novembro",
"Dezembro",
],
},
rangeSeparator: " até ",
time_24hr: true,
};
fp.l10ns.pt = Portuguese;
var pt = fp.l10ns;
exports.Portuguese = Portuguese;
exports.default = pt;
Object.defineProperty(exports, '__esModule', { value: true });
})));

75
asset/js/vendor/flatpickr/l10n/ru.js vendored Normal file
View File

@ -0,0 +1,75 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.icinga ? define(["exports"], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ru = {}));
}(this, (function (exports) { 'use strict';
var fp = typeof window !== "undefined" && window.flatpickr !== undefined
? window.flatpickr
: {
l10ns: {},
};
var Russian = {
weekdays: {
shorthand: ["Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"],
longhand: [
"Воскресенье",
"Понедельник",
"Вторник",
"Среда",
"Четверг",
"Пятница",
"Суббота",
],
},
months: {
shorthand: [
"Янв",
"Фев",
"Март",
"Апр",
"Май",
"Июнь",
"Июль",
"Авг",
"Сен",
"Окт",
"Ноя",
"Дек",
],
longhand: [
"Январь",
"Февраль",
"Март",
"Апрель",
"Май",
"Июнь",
"Июль",
"Август",
"Сентябрь",
"Октябрь",
"Ноябрь",
"Декабрь",
],
},
firstDayOfWeek: 1,
ordinal: function () {
return "";
},
rangeSeparator: " — ",
weekAbbreviation: "Нед.",
scrollTitle: "Прокрутите для увеличения",
toggleTitle: "Нажмите для переключения",
amPM: ["ДП", "ПП"],
yearAriaLabel: "Год",
time_24hr: true,
};
fp.l10ns.ru = Russian;
var ru = fp.l10ns;
exports.Russian = Russian;
exports.default = ru;
Object.defineProperty(exports, '__esModule', { value: true });
})));

66
asset/js/vendor/flatpickr/l10n/uk.js vendored Normal file
View File

@ -0,0 +1,66 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.icinga ? define(["exports"], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.uk = {}));
}(this, (function (exports) { 'use strict';
var fp = typeof window !== "undefined" && window.flatpickr !== undefined
? window.flatpickr
: {
l10ns: {},
};
var Ukrainian = {
firstDayOfWeek: 1,
weekdays: {
shorthand: ["Нд", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"],
longhand: [
"Неділя",
"Понеділок",
"Вівторок",
"Середа",
"Четвер",
"П'ятниця",
"Субота",
],
},
months: {
shorthand: [
"Січ",
"Лют",
"Бер",
"Кві",
"Тра",
"Чер",
"Лип",
"Сер",
"Вер",
"Жов",
"Лис",
"Гру",
],
longhand: [
"Січень",
"Лютий",
"Березень",
"Квітень",
"Травень",
"Червень",
"Липень",
"Серпень",
"Вересень",
"Жовтень",
"Листопад",
"Грудень",
],
},
time_24hr: true,
};
fp.l10ns.uk = Ukrainian;
var uk = fp.l10ns;
exports.Ukrainian = Ukrainian;
exports.default = uk;
Object.defineProperty(exports, '__esModule', { value: true });
})));

View File

@ -0,0 +1,990 @@
define(["../notjQuery", "Completer"], function ($, Completer) {
"use strict";
class BaseInput {
constructor(input) {
this.input = input;
this.disabled = false;
this.separator = '';
this.usedTerms = [];
this.completer = null;
this.lastCompletedTerm = null;
this._dataInput = null;
this._termInput = null;
this._termContainer = null;
}
get dataInput() {
if (this._dataInput === null) {
this._dataInput = document.querySelector(this.input.dataset.dataInput);
}
return this._dataInput;
}
get termInput() {
if (this._termInput === null) {
this._termInput = document.querySelector(this.input.dataset.termInput);
}
return this._termInput;
}
get termContainer() {
if (this._termContainer === null) {
this._termContainer = document.querySelector(this.input.dataset.termContainer);
}
return this._termContainer;
}
bind() {
// Form submissions
$(this.input.form).on('submit', this.onSubmit, this);
$(this.input.form).on(
'click', 'button:not([type]), button[type="submit"], input[type="submit"]', this.onButtonClick, this);
// User interactions
$(this.input).on('input', this.onInput, this);
$(this.input).on('keydown', this.onKeyDown, this);
$(this.input).on('keyup', this.onKeyUp, this);
$(this.input).on('blur', this.onInputBlur, this);
$(this.input).on('focusin', this.onTermFocus, this);
$(this.termContainer).on('input', '[data-label]', this.onInput, this);
$(this.termContainer).on('keydown', '[data-label]', this.onKeyDown, this);
$(this.termContainer).on('keyup', '[data-label]', this.onKeyUp, this);
$(this.termContainer).on('focusout', '[data-index]', this.onTermFocusOut, this);
$(this.termContainer).on('focusin', '[data-index]', this.onTermFocus, this);
// Copy/Paste
$(this.input).on('paste', this.onPaste, this);
$(this.input).on('copy', this.onCopyAndCut, this);
$(this.input).on('cut', this.onCopyAndCut, this);
// Should terms be completed?
if (this.input.dataset.suggestUrl) {
if (this.completer === null) {
this.completer = new Completer(this.input, true);
this.completer.bind(this.termContainer);
}
$(this.input).on('suggestion', this.onSuggestion, this);
$(this.input).on('completion', this.onCompletion, this);
$(this.termContainer).on('suggestion', '[data-label]', this.onSuggestion, this);
$(this.termContainer).on('completion', '[data-label]', this.onCompletion, this);
}
return this;
}
refresh(input) {
if (input === this.input) {
// If the DOM node is still the same, nothing has changed
return;
}
this._termInput = null;
this._termContainer = null;
this.input = input;
this.bind();
if (this.completer !== null) {
this.completer.refresh(input, this.termContainer);
}
if (! this.restoreTerms()) {
this.reset();
}
}
reset() {
this.usedTerms = [];
this.lastCompletedTerm = null;
this.togglePlaceholder();
this.termInput.value = '';
this.termContainer.innerHTML = '';
}
destroy() {
this._termContainer = null;
this._termInput = null;
this.input = null;
if (this.completer !== null) {
this.completer.destroy();
this.completer = null;
}
}
disable() {
this.disabled = true;
this.input.disabled = true;
this.input.form.classList.add('disabled');
this.termContainer.querySelectorAll('[data-index]').forEach(el => el.firstChild.disabled = true);
if (this.completer !== null) {
this.completer.reset();
}
}
enable() {
this.input.disabled = false;
this.input.form.classList.remove('disabled');
this.termContainer.querySelectorAll('[data-index]').forEach(el => el.firstChild.disabled = false);
this.disabled = false;
}
restoreTerms() {
if (this.hasTerms()) {
this.usedTerms.forEach((termData, termIndex) => this.addTerm(termData, termIndex));
this.togglePlaceholder();
this.clearPartialTerm(this.input);
} else {
this.registerTerms();
this.togglePlaceholder();
}
return this.hasTerms();
}
registerTerms() {
this.termContainer.querySelectorAll('[data-index]').forEach((label) => {
let termData = { ...label.dataset };
delete termData.index;
if (label.className) {
termData['class'] = label.className;
}
if (label.title) {
termData['title'] = label.title;
}
this.registerTerm(this.decodeTerm(termData), label.dataset.index);
});
}
registerTerm(termData, termIndex = null) {
if (termIndex !== null) {
this.usedTerms.splice(termIndex, 0, termData);
return termIndex;
} else {
return this.usedTerms.push(termData) - 1;
}
}
updateTerms(changedTerms) {
// Reset the data input, otherwise the value remains and is sent continuously with subsequent requests
this.dataInput.value = '';
if (changedTerms === 'bogus') {
return;
}
let changedIndices = Object.keys(changedTerms);
if (! changedIndices.length) {
// Perform a partial reset. this.reset() empties the termContainer, which isn't desired here
this.usedTerms = [];
this.lastCompletedTerm = null;
this.registerTerms();
this.togglePlaceholder();
this.termInput.value = '';
}
for (const termIndex of changedIndices) {
let label = this.termContainer.querySelector(`[data-index="${ termIndex }"]`);
if (! label) {
continue;
}
let input = label.firstChild;
let termData = changedTerms[termIndex];
if (termData.label) {
this.writePartialTerm(termData.label, input);
}
this.updateTermData(termData, input);
this.usedTerms[termIndex] = termData;
}
}
clearPartialTerm(input) {
if (this.completer !== null) {
this.completer.reset();
}
this.writePartialTerm('', input);
}
writePartialTerm(value, input) {
input.value = value;
this.updateTermData({ label: value }, input);
}
readPartialTerm(input) {
return input.value.trim();
}
readFullTerm(input, termIndex = null) {
let value = this.readPartialTerm(input);
if (! value) {
return false;
}
let termData = {};
if (termIndex !== null) {
termData = { ...this.usedTerms[termIndex] };
}
termData.label = value;
termData.search = value;
if (this.lastCompletedTerm !== null) {
if (termData.label === this.lastCompletedTerm.label) {
Object.assign(termData, this.lastCompletedTerm);
}
this.lastCompletedTerm = null;
}
return termData;
}
exchangeTerm() {
if (this.completer !== null) {
this.completer.reset();
}
let termData = this.readFullTerm(this.input);
if (! termData) {
return {};
}
let addedTerms = {};
if (Array.isArray(termData)) {
for (let data of termData) {
this.addTerm(data);
addedTerms[this.usedTerms.length - 1] = data;
}
} else {
this.addTerm(termData);
addedTerms[this.usedTerms.length - 1] = termData;
}
this.clearPartialTerm(this.input);
return addedTerms;
}
insertTerm(termData, termIndex) {
this.reIndexTerms(termIndex, 1, true);
this.registerTerm(termData, termIndex);
return this.insertRenderedTerm(this.renderTerm(termData, termIndex));
}
insertRenderedTerm(label) {
let next = this.termContainer.querySelector(`[data-index="${ label.dataset.index + 1 }"]`);
this.termContainer.insertBefore(label, next);
return label;
}
addTerm(termData, termIndex = null) {
if (termIndex === null) {
termIndex = this.registerTerm(termData);
}
this.addRenderedTerm(this.renderTerm(termData, termIndex));
}
addRenderedTerm(label) {
this.termContainer.appendChild(label);
}
hasTerms() {
return this.usedTerms.length > 0;
}
hasSyntaxError(input) {
if (typeof input === 'undefined') {
input = this.input;
}
return 'hasSyntaxError' in input.dataset;
}
clearSyntaxError(input) {
if (typeof input === 'undefined') {
input = this.input;
}
delete input.dataset.hasSyntaxError;
input.removeAttribute('pattern');
input.removeAttribute('title');
}
getQueryString() {
return this.termsToQueryString(this.usedTerms);
}
checkValidity(input) {
if (input.pattern && ! input.checkValidity()) {
if (! input.value.match(input.pattern)) {
if (input.dataset.invalidMsg) {
input.setCustomValidity(input.dataset.invalidMsg);
}
return false;
}
// If the pattern matches, reset the custom validity, otherwise the value is still invalid.
input.setCustomValidity('');
}
// The pattern isn't set or it matches. Any other custom validity must not be accounted for here.
return true;
}
reportValidity(element) {
setTimeout(() => element.reportValidity(), 0);
}
validate(element) {
if (! this.checkValidity(element)) {
this.reportValidity(element);
return false;
}
return true;
}
saveTerm(input, updateDOM = true, force = false) {
if (! this.checkValidity(input)) {
return false;
}
let termIndex = input.parentNode.dataset.index;
let termData = this.readFullTerm(input, termIndex);
// Only save if something has changed, unless forced
if (termData === false) {
console.warn('[BaseInput] Input is empty, cannot save');
} else if (force || this.usedTerms[termIndex].label !== termData.label) {
let oldTermData = this.usedTerms[termIndex];
this.usedTerms[termIndex] = termData;
this.updateTermData(termData, input);
return oldTermData;
}
return false;
}
updateTermData(termData, input) {
let label = input.parentNode;
label.dataset.label = termData.label;
if (!! termData.search || termData.search === '') {
label.dataset.search = termData.search;
}
if (!! termData.title) {
label.title = termData.title;
} else {
label.title = '';
}
if (termData.pattern) {
input.pattern = termData.pattern;
delete termData.pattern;
if (termData.invalidMsg) {
input.dataset.invalidMsg = termData.invalidMsg;
delete termData.invalidMsg;
}
this.validate(input);
}
}
termsToQueryString(terms) {
return terms.map(e => this.encodeTerm(e).search).join(this.separator).trim();
}
lastTerm() {
if (! this.hasTerms()) {
return null;
}
return this.usedTerms[this.usedTerms.length - 1];
}
popTerm() {
let lastTermIndex = this.usedTerms.length - 1;
return this.removeTerm(this.termContainer.querySelector(`[data-index="${ lastTermIndex }"]`));
}
removeTerm(label, updateDOM = true) {
if (this.completer !== null) {
this.completer.reset();
}
let termIndex = Number(label.dataset.index);
// Re-index following remaining terms
this.reIndexTerms(termIndex);
// Cut the term's data
let [termData] = this.usedTerms.splice(termIndex, 1);
// Avoid saving the term, it's removed after all
label.firstChild.skipSaveOnBlur = true;
if (updateDOM) {
// Remove it from the DOM
this.removeRenderedTerm(label);
}
return termData;
}
removeRenderedTerm(label) {
label.remove();
}
removeRange(labels) {
let from = Number(labels[0].dataset.index);
let to = Number(labels[labels.length - 1].dataset.index);
let deleteCount = to - from + 1;
if (to < this.usedTerms.length - 1) {
// Only re-index if there's something left
this.reIndexTerms(to, deleteCount);
}
let removedData = this.usedTerms.splice(from, deleteCount);
this.removeRenderedRange(labels);
let removedTerms = {};
for (let i = from; removedData.length; i++) {
removedTerms[i] = removedData.shift();
}
return removedTerms;
}
removeRenderedRange(labels) {
labels.forEach(label => this.removeRenderedTerm(label));
}
reIndexTerms(from, howMuch = 1, forward = false) {
if (forward) {
for (let i = this.usedTerms.length - 1; i >= from; i--) {
let label = this.termContainer.querySelector(`[data-index="${ i }"]`);
label.dataset.index = `${ i + howMuch }`;
}
} else {
for (let i = ++from; i < this.usedTerms.length; i++) {
let label = this.termContainer.querySelector(`[data-index="${ i }"]`);
label.dataset.index = `${ i - howMuch }`;
}
}
}
complete(input, data) {
if (this.completer !== null) {
$(input).trigger('complete', data);
}
}
selectTerms() {
this.termContainer.querySelectorAll('[data-index]').forEach(el => el.classList.add('selected'));
}
deselectTerms() {
this.termContainer.querySelectorAll('.selected').forEach(el => el.classList.remove('selected'));
}
clearSelectedTerms() {
if (this.hasTerms()) {
let labels = this.termContainer.querySelectorAll('.selected');
if (labels.length) {
return this.removeRange(Array.from(labels));
}
}
return {};
}
togglePlaceholder() {
let placeholder = '';
if (! this.hasTerms()) {
if (this.input.dataset.placeholder) {
placeholder = this.input.dataset.placeholder;
} else {
return;
}
} else if (this.input.placeholder) {
if (! this.input.dataset.placeholder) {
this.input.dataset.placeholder = this.input.placeholder;
}
}
this.input.placeholder = placeholder;
}
renderTerm(termData, termIndex) {
let label = $.render('<label><input type="text"></label>');
if (termData.class) {
label.classList.add(termData.class);
}
if (termData.title) {
label.title = termData.title;
}
label.dataset.label = termData.label;
label.dataset.search = termData.search;
label.dataset.index = termIndex;
label.firstChild.value = termData.label;
return label;
}
encodeTerm(termData) {
termData = { ...termData };
termData.search = encodeURIComponent(termData.search);
return termData;
}
decodeTerm(termData) {
termData.search = decodeURIComponent(termData.search);
return termData;
}
shouldNotAutoSubmit() {
return 'noAutoSubmit' in this.input.dataset;
}
autoSubmit(input, changeType, data) {
if (this.shouldNotAutoSubmit()) {
return;
}
if (changeType === 'save' && 'terms' in data) {
// Replace old term data with the new one, as required by the backend
for (const termIndex of Object.keys(data['terms'])) {
data['terms'][termIndex] = this.usedTerms[termIndex];
}
}
if (changeType === 'remove' && ! Object.keys(data['terms']).length) {
return;
}
this.dataInput.value = JSON.stringify({
type: changeType,
...data
});
let eventData = { submittedBy: input };
if (changeType === 'paste') {
// Ensure that what's pasted is also transmitted as value
eventData['terms'] = this.termsToQueryString(data['terms']) + this.separator + data['input'];
}
$(this.input.form).trigger('submit', eventData);
}
submitTerms(terms) {
$(this.input.form).trigger(
'submit',
{ terms: terms }
);
}
moveFocusForward(from = null) {
let toFocus;
let inputs = Array.from(this.termContainer.querySelectorAll('input'));
if (from === null) {
let focused = this.termContainer.querySelector('input:focus');
from = inputs.indexOf(focused);
}
if (from === -1) {
toFocus = inputs.shift();
if (typeof toFocus === 'undefined') {
toFocus = this.input;
}
} else if (from + 1 < inputs.length) {
toFocus = inputs[from + 1];
} else {
toFocus = this.input;
}
toFocus.selectionStart = toFocus.selectionEnd = 0;
$(toFocus).focus();
return toFocus;
}
moveFocusBackward(from = null) {
let toFocus;
let inputs = Array.from(this.termContainer.querySelectorAll('input'));
if (from === null) {
let focused = this.termContainer.querySelector('input:focus');
from = inputs.indexOf(focused);
}
if (from === -1) {
toFocus = inputs.pop();
} else if (from > 0 && from - 1 < inputs.length) {
toFocus = inputs[from - 1];
} else {
toFocus = this.input;
}
toFocus.selectionStart = toFocus.selectionEnd = toFocus.value.length;
$(toFocus).focus();
return toFocus;
}
/**
* Event listeners
*/
onSubmit(event) {
// Unset the input's name, to prevent its submission (It may actually have a name, as no-js fallback)
this.input.name = '';
// Set the hidden input's value, it's what's sent
if (event.detail && 'terms' in event.detail) {
this.termInput.value = event.detail.terms;
} else {
let renderedTerms = this.termsToQueryString(this.usedTerms);
if (this.hasSyntaxError()) {
renderedTerms += this.input.value;
}
this.termInput.value = renderedTerms;
}
// Enable the hidden input, otherwise it's not submitted
this.termInput.disabled = false;
}
onSuggestion(event) {
let data = event.detail;
let input = event.target;
let termData;
if (typeof data === 'object') {
termData = data;
} else {
termData = { label: data, search: data };
}
this.lastCompletedTerm = termData;
this.writePartialTerm(termData.label, input);
}
onCompletion(event) {
let input = event.target;
let termData = event.detail;
let termIndex = Number(input.parentNode.dataset.index);
this.lastCompletedTerm = termData;
this.writePartialTerm(termData.label, input);
this.checkValidity(input);
if (termIndex >= 0) {
this.autoSubmit(input, 'save', { terms: { [termIndex]: this.saveTerm(input, false, true) } });
} else {
this.autoSubmit(input, 'exchange', { terms: this.exchangeTerm() });
this.togglePlaceholder();
}
}
onInput(event) {
let input = event.target;
let isTerm = input.parentNode.dataset.index >= 0;
let termData = { label: this.readPartialTerm(input) };
this.updateTermData(termData, input);
if (! input.value && this.hasSyntaxError(input)) {
this.clearSyntaxError(input);
}
if (! this.hasSyntaxError(input)) {
if (isTerm && ! this.validate(input)) {
return;
}
this.complete(input, { term: termData });
}
if (! isTerm) {
this.autoSubmit(this.input, 'remove', { terms: this.clearSelectedTerms() });
this.togglePlaceholder();
}
}
onKeyDown(event) {
let input = event.target;
let termIndex = Number(input.parentNode.dataset.index);
if (this.hasSyntaxError(input) && ! (/[A-Z]/.test(event.key.charAt(0)) || event.ctrlKey || event.metaKey)) {
// Clear syntax error flag if the user types entirely new input after having selected the entire input
// (This way the input isn't empty but switches from input to input immediately, causing the clearing
// in onInput to not work)
if (input.selectionEnd - input.selectionStart === input.value.length) {
this.clearSyntaxError(input);
}
}
let removedTerms;
switch (event.key) {
case ' ':
if (! this.readPartialTerm(input)) {
this.complete(input, { term: { label: '' } });
event.preventDefault();
}
break;
case 'Backspace':
removedTerms = this.clearSelectedTerms();
if (termIndex >= 0 && ! input.value) {
let removedTerm = this.removeTerm(input.parentNode);
if (removedTerm !== false) {
input = this.moveFocusBackward(termIndex);
if (event.ctrlKey || event.metaKey) {
this.clearPartialTerm(input);
} else {
this.writePartialTerm(input.value.slice(0, -1), input);
}
removedTerms[termIndex] = removedTerm;
event.preventDefault();
}
} else if (isNaN(termIndex)) {
if (! input.value && this.hasTerms()) {
let termData = this.popTerm();
if (! event.ctrlKey && ! event.metaKey) {
// Removing the last char programmatically is not
// necessary since the browser default is not prevented
this.writePartialTerm(termData.label, input);
}
removedTerms[this.usedTerms.length] = termData;
}
}
this.togglePlaceholder();
this.autoSubmit(input, 'remove', { terms: removedTerms });
break;
case 'Delete':
removedTerms = this.clearSelectedTerms();
if (termIndex >= 0 && ! input.value) {
let removedTerm = this.removeTerm(input.parentNode);
if (removedTerm !== false) {
input = this.moveFocusForward(termIndex - 1);
if (event.ctrlKey || event.metaKey) {
this.clearPartialTerm(input);
} else {
this.writePartialTerm(input.value.slice(1), input);
}
removedTerms[termIndex] = removedTerm;
event.preventDefault();
}
}
this.togglePlaceholder();
this.autoSubmit(input, 'remove', { terms: removedTerms });
break;
case 'Enter':
if (termIndex >= 0) {
if (this.readPartialTerm(input)) {
this.saveTerm(input, false);
} else {
this.removeTerm(input.parentNode, false);
}
}
break;
case 'ArrowLeft':
if (input.selectionStart === 0 && this.hasTerms()) {
event.preventDefault();
this.moveFocusBackward();
}
break;
case 'ArrowRight':
if (input.selectionStart === input.value.length && this.hasTerms()) {
event.preventDefault();
this.moveFocusForward();
}
break;
case 'a':
if ((event.ctrlKey || event.metaKey) && ! this.readPartialTerm(input)) {
this.selectTerms();
}
}
}
onKeyUp(event) {
if (event.target.parentNode.dataset.index >= 0) {
return;
}
switch (event.key) {
case 'End':
case 'ArrowLeft':
case 'ArrowRight':
this.deselectTerms();
break;
case 'Home':
if (this.input.selectionStart === 0 && this.input.selectionEnd === 0) {
if (event.shiftKey) {
this.selectTerms();
} else {
this.deselectTerms();
}
}
break;
case 'Delete':
this.autoSubmit(event.target, 'remove', { terms: this.clearSelectedTerms() });
this.togglePlaceholder();
break;
}
}
onInputBlur() {
this.deselectTerms();
}
onTermFocusOut(event) {
let input = event.target;
if (this.hasSyntaxError(input)) {
return;
}
// skipSaveOnBlur is set if the input is about to be removed anyway.
// If we remove the input as well, the other removal will fail without
// any chance to handle it. (Element.remove() blurs the input)
if (typeof input.skipSaveOnBlur === 'undefined' || ! input.skipSaveOnBlur) {
setTimeout(() => {
if (this.completer === null || ! this.completer.isBeingCompleted(input)) {
let termIndex = Number(input.parentNode.dataset.index);
if (this.readPartialTerm(input)) {
let previousTerm = this.saveTerm(input);
if (previousTerm !== false) {
this.autoSubmit(input, 'save', { terms: { [termIndex]: previousTerm } });
}
} else {
this.autoSubmit(
input, 'remove', { terms: { [termIndex]: this.removeTerm(input.parentNode) } });
}
}
}, 0);
}
}
onTermFocus(event) {
let input = event.target;
if (input.parentNode.dataset.index >= 0) {
this.validate(input);
}
if (event.detail.scripted) {
// Only request suggestions if the user manually focuses the term
return;
}
this.deselectTerms();
if (! this.hasSyntaxError(input) && (
this.completer === null || ! this.completer.isBeingCompleted(input, false)
)) {
// Only request suggestions if the input is valid and not already being completed
let value = this.readPartialTerm(input);
this.complete(input, { trigger: 'script', term: { label: value } });
}
}
onButtonClick(event) {
if (! this.hasSyntaxError()) {
// Register current input value, otherwise it's not included
this.exchangeTerm();
}
if (this.hasTerms()) {
this.input.required = false;
// This is not part of `onSubmit()` because otherwise it would override what `autoSubmit()` does
this.dataInput.value = JSON.stringify({ type: 'submit', terms: this.usedTerms });
} else if (typeof this.input.dataset.manageRequired !== 'undefined') {
this.input.required = true;
}
}
onPaste(event) {
if (this.shouldNotAutoSubmit() || this.input.value) {
return;
}
this.autoSubmit(this.input, 'paste', {
input: event.clipboardData.getData('text/plain'),
terms: this.usedTerms
});
event.preventDefault();
}
onCopyAndCut(event) {
if (! this.hasTerms()) {
return;
}
let data = '';
let selectedTerms = this.termContainer.querySelectorAll('.selected');
if (selectedTerms.length) {
data = Array.from(selectedTerms).map(label => label.dataset.search).join(this.separator);
}
if (this.input.selectionStart < this.input.selectionEnd) {
data += this.separator + this.input.value.slice(this.input.selectionStart, this.input.selectionEnd);
}
event.clipboardData.setData('text/plain', data);
event.preventDefault();
if (event.type === 'cut') {
this.clearPartialTerm(this.input);
this.autoSubmit(this.input, 'remove', { terms: this.clearSelectedTerms() });
this.togglePlaceholder();
}
}
}
return BaseInput;
});

View File

@ -0,0 +1,521 @@
define(["../notjQuery"], function ($) {
"use strict";
class Completer {
constructor(input, instrumented = false) {
this.input = input;
this.instrumented = instrumented;
this.nextSuggestion = null;
this.activeSuggestion = null;
this.suggestionKiller = null;
this.completedInput = null;
this.completedValue = null;
this.completedData = null;
this._termSuggestions = null;
}
get termSuggestions() {
if (this._termSuggestions === null) {
this._termSuggestions = document.querySelector(this.input.dataset.termSuggestions);
}
return this._termSuggestions;
}
bind(to = null) {
// Form submissions
$(this.input.form).on('submit', this.onSubmit, this);
// User interactions
$(this.termSuggestions).on('focusout', '[type="button"]', this.onFocusOut, this);
$(this.termSuggestions).on('click', '[type="button"]', this.onSuggestionClick, this);
$(this.termSuggestions).on('keydown', '[type="button"]', this.onSuggestionKeyDown, this);
if (this.instrumented) {
if (to !== null) {
$(to).on('focusout', 'input[type="text"]', this.onFocusOut, this);
$(to).on('keydown', 'input[type="text"]', this.onKeyDown, this);
$(to).on('complete', 'input[type="text"]', this.onComplete, this);
}
$(this.input).on('complete', this.onComplete, this);
} else {
$(this.input).on('input', this.onInput, this);
}
$(this.input).on('focusout', this.onFocusOut, this);
$(this.input).on('keydown', this.onKeyDown, this);
return this;
}
refresh(input, bindTo = null) {
if (input === this.input) {
// If the DOM node is still the same, nothing has changed
return;
}
this._termSuggestions = null;
this.abort();
this.input = input;
this.bind(bindTo);
}
reset() {
this.abort();
this.hideSuggestions();
}
destroy() {
this._termSuggestions = null;
this.input = null;
}
renderSuggestions(html) {
let template = document.createElement('template');
template.innerHTML = html;
return template.content;
}
showSuggestions(suggestions, input) {
this.termSuggestions.innerHTML = '';
this.termSuggestions.appendChild(suggestions);
this.termSuggestions.style.display = '';
let containingBlock = this.termSuggestions.offsetParent || document.body;
let containingBlockRect = containingBlock.getBoundingClientRect();
let inputRect = input.getBoundingClientRect();
let inputPosX = inputRect.left - containingBlockRect.left;
let inputPosY = inputRect.bottom - containingBlockRect.top;
let suggestionWidth = this.termSuggestions.offsetWidth;
let maxAvailableHeight = document.body.clientHeight - inputRect.bottom;
let localMarginBottom = window.getComputedStyle(this.termSuggestions).marginBottom;
this.termSuggestions.style.top = `${ inputPosY }px`;
this.termSuggestions.style.maxHeight = `calc(${maxAvailableHeight}px - ${localMarginBottom})`;
if (inputPosX + suggestionWidth > containingBlockRect.right - containingBlockRect.left) {
this.termSuggestions.style.left =
`${ containingBlockRect.right - containingBlockRect.left - suggestionWidth }px`;
} else {
this.termSuggestions.style.left = `${ inputPosX }px`;
}
}
hasSuggestions() {
return this.termSuggestions.childNodes.length > 0;
}
hideSuggestions() {
if (this.nextSuggestion !== null || this.activeSuggestion !== null) {
return;
}
if (this.suggestionKiller !== null) {
// onFocusOut initiates this timer in order to hide the suggestions if the user
// doesn't navigate them. Since it does this by checking after a short interval
// if the focus is inside the suggestions, the interval has to be long enough to
// have a chance to detect the focus. `focusout` runs before `blur` and `focus`,
// so this may lead to a race condition which is addressed by the timeout. Though,
// to not close the newly opened suggestions of the next input the timer has to
// be cancelled here since it's purpose is already fulfilled.
clearTimeout(this.suggestionKiller);
this.suggestionKiller = null;
}
this.termSuggestions.style.display = 'none';
this.termSuggestions.innerHTML = '';
this.completedInput = null;
this.completedValue = null;
this.completedData = null;
}
prepareCompletionData(input, data = null) {
if (data === null) {
data = { term: { ...input.dataset } };
data.term.label = input.value;
}
let value = data.term.label;
data.term.search = value;
data.term.label = this.addWildcards(value);
if (input.parentElement instanceof HTMLFieldSetElement) {
for (let element of input.parentElement.elements) {
if (element !== input
&& element.name !== input.name + '-search'
&& (element.name.substr(-7) === '-search'
|| typeof input.form[element.name + '-search'] === 'undefined')
) {
// Make sure we'll use a key that the server can understand..
let dataName = element.name;
if (dataName.substr(-7) === '-search') {
dataName = dataName.substr(0, dataName.length - 7);
}
if (dataName.substr(0, input.parentElement.name.length) === input.parentElement.name) {
dataName = dataName.substr(input.parentElement.name.length);
}
if (! dataName in data || element.value) {
data[dataName] = element.value;
}
}
}
}
return [value, data];
}
addWildcards(value) {
if (! value) {
return '*';
}
if (value.slice(0, 1) !== '*' && value.slice(-1) !== '*') {
return '*' + value + '*';
}
return value;
}
abort() {
if (this.activeSuggestion !== null) {
this.activeSuggestion.abort();
this.activeSuggestion = null;
}
if (this.nextSuggestion !== null) {
clearTimeout(this.nextSuggestion);
this.nextSuggestion = null;
}
}
requestCompletion(input, data, trigger = 'user') {
this.abort();
this.nextSuggestion = setTimeout(() => {
let req = new XMLHttpRequest();
req.open('POST', this.input.dataset.suggestUrl, true);
req.setRequestHeader('Content-Type', 'application/json');
if (typeof icinga !== 'undefined') {
let windowId = icinga.ui.getWindowId();
let containerId = icinga.ui.getUniqueContainerId(this.termSuggestions);
if (containerId) {
req.setRequestHeader('X-Icinga-WindowId', windowId + '_' + containerId);
} else {
req.setRequestHeader('X-Icinga-WindowId', windowId);
}
}
req.addEventListener('loadend', () => {
if (req.readyState > 0) {
if (req.responseText) {
let suggestions = this.renderSuggestions(req.responseText);
if (trigger === 'script') {
// If the suggestions are to be displayed due to a scripted event,
// show them only if the completed input is still focused..
if (document.activeElement === input) {
this.showSuggestions(suggestions, input);
}
} else {
this.showSuggestions(suggestions, input);
}
} else {
this.hideSuggestions();
}
}
this.activeSuggestion = null;
this.nextSuggestion = null;
});
req.send(JSON.stringify(data));
this.activeSuggestion = req;
}, 200);
}
suggest(input, value, data = {}) {
if (this.instrumented) {
if (! Object.keys(data).length) {
data = value;
}
$(input).trigger('suggestion', data);
} else {
input.value = value;
}
}
complete(input, value, data) {
$(input).focus({ scripted: true });
if (this.instrumented) {
if (! Object.keys(data).length) {
data = value;
}
$(input).trigger('completion', data);
} else {
input.value = value;
for (let name in data) {
let dataElement = input.form[input.name + '-' + name];
if (typeof dataElement !== 'undefined') {
if (dataElement instanceof RadioNodeList) {
dataElement = dataElement[dataElement.length - 1];
}
dataElement.value = data[name];
} else if (name === 'title') {
input.title = data[name];
}
}
}
this.hideSuggestions();
}
moveToSuggestion(backwards = false) {
let focused = this.termSuggestions.querySelector('[type="button"]:focus');
let inputs = Array.from(this.termSuggestions.querySelectorAll('[type="button"]'));
let input;
if (focused !== null) {
let sibling = inputs[backwards ? inputs.indexOf(focused) - 1 : inputs.indexOf(focused) + 1];
if (sibling) {
input = sibling;
} else {
input = this.completedInput;
}
} else {
input = inputs[backwards ? inputs.length - 1 : 0];
}
$(input).focus();
if (this.completedValue !== null) {
if (input === this.completedInput) {
this.suggest(this.completedInput, this.completedValue);
} else {
this.suggest(this.completedInput, input.value, { ...input.dataset });
}
}
return input;
}
isBeingCompleted(input, activeElement = null) {
if (activeElement === null) {
activeElement = document.activeElement;
}
return input === this.completedInput && this.hasSuggestions()
&& (! activeElement || input === activeElement || this.termSuggestions.contains(activeElement));
}
/**
* Event listeners
*/
onSubmit(event) {
// Reset all states, the user is about to navigate away
this.reset();
}
onFocusOut(event) {
if (this.completedInput === null) {
// If there are multiple instances of Completer bound to the same suggestion container
// all of them try to handle the event. Though, only one of them is responsible and
// that's the one which has a completed input set.
return;
}
let input = event.target;
let completedInput = this.completedInput;
this.suggestionKiller = setTimeout(() => {
if (completedInput !== this.completedInput) {
// Don't hide another input's suggestions
} else if (document.activeElement !== completedInput
&& ! this.termSuggestions.contains(document.activeElement)
) {
// Hide the suggestions if the user doesn't navigate them
if (input !== completedInput) {
// Restore input if a suggestion lost focus
this.suggest(completedInput, this.completedValue);
}
this.hideSuggestions();
}
}, 250);
}
onSuggestionKeyDown(event) {
if (this.completedInput === null) {
return;
}
switch (event.key) {
case 'Escape':
$(this.completedInput).focus({ scripted: true });
this.suggest(this.completedInput, this.completedValue);
break;
case 'Tab':
event.preventDefault();
this.moveToSuggestion(event.shiftKey);
break;
case 'ArrowLeft':
case 'ArrowUp':
event.preventDefault();
this.moveToSuggestion(true);
break;
case 'ArrowRight':
case 'ArrowDown':
event.preventDefault();
this.moveToSuggestion();
break;
}
}
onSuggestionClick(event) {
if (this.completedInput === null) {
return;
}
let input = event.currentTarget;
this.complete(this.completedInput, input.value, { ...input.dataset });
}
onKeyDown(event) {
let suggestions;
switch (event.key) {
case ' ':
if (this.instrumented) {
break;
}
let input = event.target;
if (! input.value) {
if (input.minLength <= 0) {
let [value, data] = this.prepareCompletionData(input);
this.completedInput = input;
this.completedValue = value;
this.completedData = data;
this.requestCompletion(input, data);
}
event.preventDefault();
}
break;
case 'Tab':
suggestions = this.termSuggestions.querySelectorAll('[type="button"]');
if (suggestions.length === 1) {
event.preventDefault();
let input = event.target;
let suggestion = suggestions[0];
this.complete(input, suggestion.value, { ...suggestion.dataset });
}
break;
case 'Enter':
let defaultSuggestion = this.termSuggestions.querySelector('.default > [type="button"]');
if (defaultSuggestion !== null) {
event.preventDefault();
let input = event.target;
this.complete(input, defaultSuggestion.value, { ...defaultSuggestion.dataset });
}
break;
case 'Escape':
if (this.hasSuggestions()) {
this.hideSuggestions()
event.preventDefault();
}
break;
case 'ArrowUp':
suggestions = this.termSuggestions.querySelectorAll('[type="button"]');
if (suggestions.length) {
event.preventDefault();
this.moveToSuggestion(true);
}
break;
case 'ArrowDown':
suggestions = this.termSuggestions.querySelectorAll('[type="button"]');
if (suggestions.length) {
event.preventDefault();
this.moveToSuggestion();
}
break;
default:
if (/[A-Z]/.test(event.key.charAt(0)) || event.key === '"') {
// Ignore control keys not resulting in new input data
break;
}
let typedSuggestion = this.termSuggestions.querySelector(`[value="${ event.key }"]`);
if (typedSuggestion !== null) {
this.hideSuggestions();
}
}
}
onInput(event) {
let input = event.target;
if (input.minLength > 0 && input.value.length < input.minLength) {
return;
}
// Set the input's value as search value. This ensures that if the user doesn't
// choose a suggestion, an up2date contextual value will be transmitted with
// completion requests and the server can properly identify a new value upon submit
input.dataset.search = input.value;
if (typeof input.form[input.name + '-search'] !== 'undefined') {
let dataElement = input.form[input.name + '-search'];
if (dataElement instanceof RadioNodeList) {
dataElement = dataElement[dataElement.length - 1];
}
dataElement.value = input.value;
}
let [value, data] = this.prepareCompletionData(input);
this.completedInput = input;
this.completedValue = value;
this.completedData = data;
this.requestCompletion(input, data);
}
onComplete(event) {
let input = event.target;
let { trigger = 'user' , ...detail } = event.detail;
let [value, data] = this.prepareCompletionData(input, detail);
this.completedInput = input;
this.completedValue = value;
this.completedData = data;
if (typeof data.suggestions !== 'undefined') {
this.showSuggestions(data.suggestions, input);
} else {
this.requestCompletion(input, data, trigger);
}
}
}
return Completer;
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,81 @@
define(["../notjQuery"], function ($) {
"use strict";
class SearchBar {
constructor(form) {
this.form = form;
this.filterInput = null;
}
bind() {
$(this.form.parentNode).on('click', '[data-search-editor-url]', this.onOpenerClick, this);
return this;
}
refresh(form) {
if (form === this.form) {
// If the DOM node is still the same, nothing has changed
return;
}
this.form = form;
this.bind();
}
destroy() {
this.form = null;
this.filterInput = null;
}
setFilterInput(filterInput) {
this.filterInput = filterInput;
return this;
}
onOpenerClick(event) {
let opener = event.currentTarget;
let editorUrl = opener.dataset.searchEditorUrl;
let filterQueryString = this.filterInput.getQueryString();
let layout = document.getElementById('layout');
editorUrl += (editorUrl.indexOf('?') > -1 ? '&' : '?') + filterQueryString;
// Disable pointer events to block further function calls
opener.style.pointerEvents = 'none';
let observer = new MutationObserver((mutations) => {
for (let mutation of mutations) {
if (mutation.type === 'childList') {
mutation.removedNodes.forEach((node) => {
// Remove the pointerEvent none style to make the button clickable again
// after the modal has been removed
if (node.id === 'modal') {
opener.style.pointerEvents = '';
observer.disconnect();
}
});
}
}
});
observer.observe(layout, {childList: true});
// The search editor should open in a modal. We simulate a click on an anchor
// appropriately prepared so that Icinga Web 2 will handle it natively.
let a = document.createElement('a');
a.classList.add('modal-opener');
a.href = editorUrl;
a.dataset.noIcingaAjax = '';
a.dataset.icingaModal = '';
opener.parentNode.insertBefore(a, opener.nextSibling);
a.click();
a.remove();
}
}
return SearchBar;
});

View File

@ -0,0 +1,79 @@
define(["../notjQuery", "../vendor/Sortable"], function ($, Sortable) {
"use strict";
class SearchEditor {
constructor(form) {
this.form = form;
}
bind() {
$(this.form).on('end', this.onRuleDropped, this);
this.form.querySelectorAll('ol').forEach(sortable => {
let options = {
scroll: true,
group: 'rules',
direction: 'vertical',
invertSwap: true,
handle: '.drag-initiator'
};
Sortable.create(sortable, options);
});
return this;
}
refresh(form) {
if (form === this.form) {
// If the DOM node is still the same, nothing has changed
return;
}
this.form = form;
this.bind();
}
destroy() {
this.form = null;
this.filterInput = null;
}
onRuleDropped(event) {
if (event.to === event.from && event.newIndex === event.oldIndex) {
// The user dropped the rule at its previous position
return;
}
let placement = 'before';
let neighbour = event.to.querySelector(':scope > :nth-child(' + (event.newIndex + 2) + ')');
if (! neighbour) {
// User dropped the rule at the end of a group
placement = 'after';
neighbour = event.to.querySelector(':scope > :nth-child(' + event.newIndex + ')')
if (! neighbour) {
// User dropped the rule into an empty group
placement = 'to';
neighbour = event.to.closest('[id]');
}
}
// It's a submit element, the very first one, otherwise Icinga Web 2 sends another "structural-change"
this.form.insertBefore(
$.render(
'<input type="hidden" name="structural-change[1]" value="' + placement + ':' + neighbour.id + '">'
),
this.form.firstChild
);
this.form.insertBefore(
$.render('<input type="submit" name="structural-change[0]" value="move-rule:' + event.item.id + '">'),
this.form.firstChild
);
$(this.form).trigger('submit');
}
}
return SearchEditor;
});

View File

@ -0,0 +1,195 @@
define(["../notjQuery", "BaseInput"], function ($, BaseInput) {
"use strict";
class TermInput extends BaseInput {
constructor(input) {
super(input);
this.separator = this.input.dataset.termSeparator || ' ';
this.ignoreSpaceUntil = null;
}
bind() {
super.bind();
// TODO: Compatibility only. Remove as soon as possible once Web 2.12 (?) is out.
// Or upon any other update which lets Web trigger a real submit upon auto submit.
$(this.input.form).on('change', 'select.autosubmit', this.onSubmit, this);
$(this.input.form).on('change', 'input.autosubmit', this.onSubmit, this);
return this;
}
reset() {
super.reset();
this.ignoreSpaceUntil = null;
}
readPartialTerm(input) {
let value = super.readPartialTerm(input);
if (value && this.ignoreSpaceUntil && value[0] === this.ignoreSpaceUntil) {
value = value.slice(1);
if (value.slice(-1) === this.ignoreSpaceUntil) {
value = value.slice(0, -1);
}
}
return value;
}
writePartialTerm(value, input) {
if (this.ignoreSpaceUntil !== null && this.ignoreSpaceSince === 0) {
value = this.ignoreSpaceUntil + value;
}
super.writePartialTerm(value, input);
}
readFullTerm(input, termIndex = null) {
let termData = super.readFullTerm(input, termIndex);
if (termData && this.ignoreSpaceUntil !== null && input.value[0] === this.ignoreSpaceUntil) {
if (input.value.slice(-1) !== this.ignoreSpaceUntil || input.value.length < 2) {
return false;
}
this.ignoreSpaceUntil = null;
}
return termData;
}
hasSyntaxError(input) {
if ((typeof input === 'undefined' || input === this.input) && this.ignoreSpaceUntil !== null) {
if (this.input.value === this.ignoreSpaceUntil) {
return true;
}
}
return super.hasSyntaxError(input);
}
termsToQueryString(terms) {
let quoted = [];
for (const termData of terms) {
if (termData.search.indexOf(this.separator) >= 0) {
quoted.push({ ...termData, search: '"' + termData.search + '"' });
} else {
quoted.push(termData);
}
}
return super.termsToQueryString(quoted);
}
complete(input, data) {
data.exclude = this.usedTerms.map(termData => termData.search);
super.complete(input, data);
}
/**
* Event listeners
*/
onSubmit(event) {
super.onSubmit(event);
this.ignoreSpaceUntil = null;
}
onInput(event) {
let label = event.target.parentNode;
if (label.dataset.index >= 0) {
super.onInput(event);
return;
}
let input = event.target;
let firstChar = input.value[0];
if (this.ignoreSpaceUntil !== null) {
// Reset if the user changes/removes the source char
if (firstChar !== this.ignoreSpaceUntil) {
this.ignoreSpaceUntil = null;
}
}
if (this.ignoreSpaceUntil === null && (firstChar === "'" || firstChar === '"')) {
this.ignoreSpaceUntil = firstChar;
}
super.onInput(event);
}
onKeyDown(event) {
super.onKeyDown(event);
if (event.defaultPrevented) {
return;
}
let label = event.target.parentNode;
if (label.dataset.index >= 0) {
return;
}
if (event.key !== this.separator) {
return;
}
let addedTerms = this.exchangeTerm();
if (Object.keys(addedTerms).length) {
this.togglePlaceholder();
event.preventDefault();
this.autoSubmit(this.input, 'exchange', { terms: addedTerms });
}
}
onKeyUp(event) {
super.onKeyUp(event);
let label = event.target.parentNode;
if (label.dataset.index >= 0) {
return;
}
if (this.ignoreSpaceUntil !== null) {
// Reset if the user changes/removes the source char
let value = event.target.value;
if (value[this.ignoreSpaceSince] !== this.ignoreSpaceUntil) {
this.ignoreSpaceUntil = null;
this.ignoreSpaceSince = null;
}
}
let input = event.target;
switch (event.key) {
case '"':
case "'":
if (this.ignoreSpaceUntil === null) {
this.ignoreSpaceUntil = event.key;
this.ignoreSpaceSince = input.selectionStart - 1;
}
}
}
onButtonClick(event) {
if (! this.hasSyntaxError()) {
let addedTerms = this.exchangeTerm();
if (Object.keys(addedTerms).length) {
this.togglePlaceholder();
event.preventDefault();
this.autoSubmit(this.input, 'exchange', { terms: addedTerms });
this.ignoreSpaceUntil = null;
return;
}
}
super.onButtonClick(event);
}
}
return TermInput;
});

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
<svg height="32" viewBox="0 0 24 32" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m5.20126707.78766623 4.45386238 4.20402191c.16721345.15783356.16291017.40979541-.00961164.56277256-.081158.0719638-.18974398.11220597-.3027668.11220597h-8.90772462c-.24025844 0-.43502639-.17818569-.43502639-.39798892 0-.10340014.04398717-.20274128.12264801-.27698961l4.45386234-4.20402191c.16721345-.15783357.44262326-.16177048.61514507-.00879333.00325382.00288518.00645805.00581661.00961165.00879333z" fill="#282E39" transform="matrix(1 0 0 -1 7 20.666667)"/></svg>

After

Width:  |  Height:  |  Size: 559 B

View File

@ -0,0 +1 @@
<svg height="32" viewBox="0 0 24 32" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m5.20126707.78766623 4.45386238 4.20402191c.16721345.15783356.16291017.40979541-.00961164.56277256-.081158.0719638-.18974398.11220597-.3027668.11220597h-8.90772462c-.24025844 0-.43502639-.17818569-.43502639-.39798892 0-.10340014.04398717-.20274128.12264801-.27698961l4.45386234-4.20402191c.16721345-.15783357.44262326-.16177048.61514507-.00879333.00325382.00288518.00645805.00581661.00961165.00879333z" fill="#00c3ed" transform="matrix(1 0 0 -1 7 20.666667)"/></svg>

After

Width:  |  Height:  |  Size: 558 B

1395
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

25
vendor/autoload.php vendored Normal file
View File

@ -0,0 +1,25 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit4b54b89637b513998e2a3c6fdc91f8cc::getLoader();

585
vendor/composer/ClassLoader.php vendored Normal file
View File

@ -0,0 +1,585 @@
<?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 https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var ?string */
private $vendorDir;
// PSR-4
/**
* @var array[]
* @psalm-var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, array<int, string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* @var array[]
* @psalm-var array<string, array<string, string[]>>
*/
private $prefixesPsr0 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var string[]
* @psalm-var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var bool[]
* @psalm-var array<string, bool>
*/
private $missingClasses = array();
/** @var ?string */
private $apcuPrefix;
/**
* @var self[]
*/
private static $registeredLoaders = array();
/**
* @param ?string $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return string[]
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array[]
* @psalm-return array<string, array<int, string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return string[] Array of classname => path
* @psalm-return array<string, string>
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param string[] $classMap Class to filename map
* @psalm-param array<string, string> $classMap
*
* @return void
*/
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 string[]|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
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 string[]|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
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 string[]|string $paths The PSR-0 base directories
*
* @return void
*/
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 string[]|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
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
*
* @return void
*/
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
*
* @return void
*/
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
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $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
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* 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;
}
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
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;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}

359
vendor/composer/InstalledVersions.php vendored Normal file
View File

@ -0,0 +1,359 @@
<?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;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
$installed[] = self::$installedByVendor[$vendorDir] = $required;
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else {
self::$installed = array();
}
}
if (self::$installed !== array()) {
$installed[] = self::$installed;
}
return $installed;
}
}

21
vendor/composer/LICENSE vendored Normal file
View File

@ -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.

10
vendor/composer/autoload_classmap.php vendored Normal file
View File

@ -0,0 +1,10 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
);

17
vendor/composer/autoload_files.php vendored Normal file
View File

@ -0,0 +1,17 @@
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'a2c78434f64e5f5ed402f42eee19c025' => $vendorDir . '/ipl/stdlib/src/functions_include.php',
'6076de347104821999fcfc82c8f19bc5' => $vendorDir . '/ipl/i18n/src/functions_include.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php',
'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php',
'8e4ccce73649a2b516ec3b4571432da5' => $vendorDir . '/ipl/scheduler/src/register_cron_aliases.php',
);

11
vendor/composer/autoload_namespaces.php vendored Normal file
View File

@ -0,0 +1,11 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Evenement' => array($vendorDir . '/evenement/evenement/src'),
'AssetLoader' => array($baseDir . '/'),
);

29
vendor/composer/autoload_psr4.php vendored Normal file
View File

@ -0,0 +1,29 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'ipl\\Web\\' => array($vendorDir . '/ipl/web/src'),
'ipl\\Validator\\' => array($vendorDir . '/ipl/validator/src'),
'ipl\\Stdlib\\' => array($vendorDir . '/ipl/stdlib/src'),
'ipl\\Sql\\' => array($vendorDir . '/ipl/sql/src'),
'ipl\\Scheduler\\' => array($vendorDir . '/ipl/scheduler/src'),
'ipl\\Orm\\' => array($vendorDir . '/ipl/orm/src'),
'ipl\\I18n\\' => array($vendorDir . '/ipl/i18n/src'),
'ipl\\Html\\' => array($vendorDir . '/ipl/html/src'),
'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
'Recurr\\' => array($vendorDir . '/simshaun/recurr/src/Recurr'),
'React\\Promise\\' => array($vendorDir . '/react/promise/src'),
'React\\EventLoop\\' => array($vendorDir . '/react/event-loop/src'),
'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
'Doctrine\\Deprecations\\' => array($vendorDir . '/doctrine/deprecations/lib/Doctrine/Deprecations'),
'Doctrine\\Common\\Collections\\' => array($vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections'),
'Cron\\' => array($vendorDir . '/dragonmantank/cron-expression/src/Cron'),
);

50
vendor/composer/autoload_real.php vendored Normal file
View File

@ -0,0 +1,50 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit4b54b89637b513998e2a3c6fdc91f8cc
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit4b54b89637b513998e2a3c6fdc91f8cc', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit4b54b89637b513998e2a3c6fdc91f8cc', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit4b54b89637b513998e2a3c6fdc91f8cc::getInitializer($loader));
$loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInit4b54b89637b513998e2a3c6fdc91f8cc::$files;
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}, null, null);
foreach ($filesToLoad as $fileIdentifier => $file) {
$requireFile($fileIdentifier, $file);
}
return $loader;
}
}

181
vendor/composer/autoload_static.php vendored Normal file
View File

@ -0,0 +1,181 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit4b54b89637b513998e2a3c6fdc91f8cc
{
public static $files = array (
'a2c78434f64e5f5ed402f42eee19c025' => __DIR__ . '/..' . '/ipl/stdlib/src/functions_include.php',
'6076de347104821999fcfc82c8f19bc5' => __DIR__ . '/..' . '/ipl/i18n/src/functions_include.php',
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
'e39a8b23c42d4e1452234d762b03835a' => __DIR__ . '/..' . '/ramsey/uuid/src/functions.php',
'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php',
'8e4ccce73649a2b516ec3b4571432da5' => __DIR__ . '/..' . '/ipl/scheduler/src/register_cron_aliases.php',
);
public static $prefixLengthsPsr4 = array (
'i' =>
array (
'ipl\\Web\\' => 8,
'ipl\\Validator\\' => 14,
'ipl\\Stdlib\\' => 11,
'ipl\\Sql\\' => 8,
'ipl\\Scheduler\\' => 14,
'ipl\\Orm\\' => 8,
'ipl\\I18n\\' => 9,
'ipl\\Html\\' => 9,
),
'W' =>
array (
'Webmozart\\Assert\\' => 17,
),
'S' =>
array (
'Symfony\\Polyfill\\Ctype\\' => 23,
),
'R' =>
array (
'Recurr\\' => 7,
'React\\Promise\\' => 14,
'React\\EventLoop\\' => 16,
'Ramsey\\Uuid\\' => 12,
),
'P' =>
array (
'Psr\\Log\\' => 8,
'Psr\\Http\\Message\\' => 17,
),
'G' =>
array (
'GuzzleHttp\\Psr7\\' => 16,
),
'D' =>
array (
'Doctrine\\Deprecations\\' => 22,
'Doctrine\\Common\\Collections\\' => 28,
),
'C' =>
array (
'Cron\\' => 5,
),
);
public static $prefixDirsPsr4 = array (
'ipl\\Web\\' =>
array (
0 => __DIR__ . '/..' . '/ipl/web/src',
),
'ipl\\Validator\\' =>
array (
0 => __DIR__ . '/..' . '/ipl/validator/src',
),
'ipl\\Stdlib\\' =>
array (
0 => __DIR__ . '/..' . '/ipl/stdlib/src',
),
'ipl\\Sql\\' =>
array (
0 => __DIR__ . '/..' . '/ipl/sql/src',
),
'ipl\\Scheduler\\' =>
array (
0 => __DIR__ . '/..' . '/ipl/scheduler/src',
),
'ipl\\Orm\\' =>
array (
0 => __DIR__ . '/..' . '/ipl/orm/src',
),
'ipl\\I18n\\' =>
array (
0 => __DIR__ . '/..' . '/ipl/i18n/src',
),
'ipl\\Html\\' =>
array (
0 => __DIR__ . '/..' . '/ipl/html/src',
),
'Webmozart\\Assert\\' =>
array (
0 => __DIR__ . '/..' . '/webmozart/assert/src',
),
'Symfony\\Polyfill\\Ctype\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
),
'Recurr\\' =>
array (
0 => __DIR__ . '/..' . '/simshaun/recurr/src/Recurr',
),
'React\\Promise\\' =>
array (
0 => __DIR__ . '/..' . '/react/promise/src',
),
'React\\EventLoop\\' =>
array (
0 => __DIR__ . '/..' . '/react/event-loop/src',
),
'Ramsey\\Uuid\\' =>
array (
0 => __DIR__ . '/..' . '/ramsey/uuid/src',
),
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
),
'Psr\\Http\\Message\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-message/src',
),
'GuzzleHttp\\Psr7\\' =>
array (
0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
),
'Doctrine\\Deprecations\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/deprecations/lib/Doctrine/Deprecations',
),
'Doctrine\\Common\\Collections\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/collections/lib/Doctrine/Common/Collections',
),
'Cron\\' =>
array (
0 => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron',
),
);
public static $prefixesPsr0 = array (
'E' =>
array (
'Evenement' =>
array (
0 => __DIR__ . '/..' . '/evenement/evenement/src',
),
),
'A' =>
array (
'AssetLoader' =>
array (
0 => __DIR__ . '/../..' . '/',
),
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit4b54b89637b513998e2a3c6fdc91f8cc::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit4b54b89637b513998e2a3c6fdc91f8cc::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit4b54b89637b513998e2a3c6fdc91f8cc::$prefixesPsr0;
$loader->classMap = ComposerStaticInit4b54b89637b513998e2a3c6fdc91f8cc::$classMap;
}, null, ClassLoader::class);
}
}

1449
vendor/composer/installed.json vendored Normal file

File diff suppressed because it is too large Load Diff

257
vendor/composer/installed.php vendored Normal file
View File

@ -0,0 +1,257 @@
<?php return array(
'root' => array(
'name' => 'icinga/icinga-php-library',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '761068221518718db19b0139f369fb8630fb3521',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => true,
),
'versions' => array(
'doctrine/collections' => array(
'pretty_version' => '1.8.0',
'version' => '1.8.0.0',
'reference' => '2b44dd4cbca8b5744327de78bafef5945c7e7b5e',
'type' => 'library',
'install_path' => __DIR__ . '/../doctrine/collections',
'aliases' => array(),
'dev_requirement' => false,
),
'doctrine/deprecations' => array(
'pretty_version' => 'v1.0.0',
'version' => '1.0.0.0',
'reference' => '0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de',
'type' => 'library',
'install_path' => __DIR__ . '/../doctrine/deprecations',
'aliases' => array(),
'dev_requirement' => false,
),
'dragonmantank/cron-expression' => array(
'pretty_version' => 'v3.3.2',
'version' => '3.3.2.0',
'reference' => '782ca5968ab8b954773518e9e49a6f892a34b2a8',
'type' => 'library',
'install_path' => __DIR__ . '/../dragonmantank/cron-expression',
'aliases' => array(),
'dev_requirement' => false,
),
'evenement/evenement' => array(
'pretty_version' => 'v3.0.1',
'version' => '3.0.1.0',
'reference' => '531bfb9d15f8aa57454f5f0285b18bec903b8fb7',
'type' => 'library',
'install_path' => __DIR__ . '/../evenement/evenement',
'aliases' => array(),
'dev_requirement' => false,
),
'fortawesome/font-awesome' => array(
'pretty_version' => '6.4.0',
'version' => '6.4.0.0',
'reference' => '0698449d50f2b95517562295a59d414afc68b369',
'type' => 'library',
'install_path' => __DIR__ . '/../fortawesome/font-awesome',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/psr7' => array(
'pretty_version' => '1.9.1',
'version' => '1.9.1.0',
'reference' => 'e4490cabc77465aaee90b20cfc9a770f8c04be6b',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/psr7',
'aliases' => array(),
'dev_requirement' => false,
),
'icinga/icinga-php-library' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '761068221518718db19b0139f369fb8630fb3521',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'ipl/html' => array(
'pretty_version' => 'v0.7.0',
'version' => '0.7.0.0',
'reference' => '8740b9efd42607e1b03a792897a9d3d51bc01e7f',
'type' => 'library',
'install_path' => __DIR__ . '/../ipl/html',
'aliases' => array(),
'dev_requirement' => false,
),
'ipl/i18n' => array(
'pretty_version' => 'v0.2.0',
'version' => '0.2.0.0',
'reference' => '3ee2a8c0c38879cb743c866d9202f8620d4c2800',
'type' => 'library',
'install_path' => __DIR__ . '/../ipl/i18n',
'aliases' => array(),
'dev_requirement' => false,
),
'ipl/orm' => array(
'pretty_version' => 'v0.5.2',
'version' => '0.5.2.0',
'reference' => 'f8cd49c09edfce8ee53aeaeba6cb76bb022e74c9',
'type' => 'library',
'install_path' => __DIR__ . '/../ipl/orm',
'aliases' => array(),
'dev_requirement' => false,
),
'ipl/scheduler' => array(
'pretty_version' => 'v0.1.1',
'version' => '0.1.1.0',
'reference' => '4afe82c0168cc91750a974d20516341957606a53',
'type' => 'library',
'install_path' => __DIR__ . '/../ipl/scheduler',
'aliases' => array(),
'dev_requirement' => false,
),
'ipl/sql' => array(
'pretty_version' => 'v0.6.0',
'version' => '0.6.0.0',
'reference' => 'cb4f5a239bfb5046a4ad7206d77a1c1d82d6cdce',
'type' => 'library',
'install_path' => __DIR__ . '/../ipl/sql',
'aliases' => array(),
'dev_requirement' => false,
),
'ipl/stdlib' => array(
'pretty_version' => 'v0.12.1',
'version' => '0.12.1.0',
'reference' => 'be75213aa3ba1c73756368f17be20a2bcbdeb835',
'type' => 'library',
'install_path' => __DIR__ . '/../ipl/stdlib',
'aliases' => array(),
'dev_requirement' => false,
),
'ipl/validator' => array(
'pretty_version' => 'v0.5.0',
'version' => '0.5.0.0',
'reference' => 'a601fae0ed330e63cea50e4a2a6659ca1ad97bde',
'type' => 'library',
'install_path' => __DIR__ . '/../ipl/validator',
'aliases' => array(),
'dev_requirement' => false,
),
'ipl/web' => array(
'pretty_version' => 'v0.8.0',
'version' => '0.8.0.0',
'reference' => 'c14a0536840972d42054cae72eae34f242fdb934',
'type' => 'library',
'install_path' => __DIR__ . '/../ipl/web',
'aliases' => array(),
'dev_requirement' => false,
),
'mtdowling/cron-expression' => array(
'dev_requirement' => false,
'replaced' => array(
0 => '^1.0',
),
),
'paragonie/random_compat' => array(
'pretty_version' => 'v9.99.100',
'version' => '9.99.100.0',
'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a',
'type' => 'library',
'install_path' => __DIR__ . '/../paragonie/random_compat',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-message' => array(
'pretty_version' => '1.1',
'version' => '1.1.0.0',
'reference' => 'cb6ce4845ce34a8ad9e68117c10ee90a29919eba',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-message',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-message-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/log' => array(
'pretty_version' => '1.1.4',
'version' => '1.1.4.0',
'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/log',
'aliases' => array(),
'dev_requirement' => false,
),
'ralouphie/getallheaders' => array(
'pretty_version' => '3.0.3',
'version' => '3.0.3.0',
'reference' => '120b605dfeb996808c31b6477290a714d356e822',
'type' => 'library',
'install_path' => __DIR__ . '/../ralouphie/getallheaders',
'aliases' => array(),
'dev_requirement' => false,
),
'ramsey/uuid' => array(
'pretty_version' => '3.9.7',
'version' => '3.9.7.0',
'reference' => 'dc75aa439eb4c1b77f5379fd958b3dc0e6014178',
'type' => 'library',
'install_path' => __DIR__ . '/../ramsey/uuid',
'aliases' => array(),
'dev_requirement' => false,
),
'react/event-loop' => array(
'pretty_version' => 'v1.4.0',
'version' => '1.4.0.0',
'reference' => '6e7e587714fff7a83dcc7025aee42ab3b265ae05',
'type' => 'library',
'install_path' => __DIR__ . '/../react/event-loop',
'aliases' => array(),
'dev_requirement' => false,
),
'react/promise' => array(
'pretty_version' => 'v2.10.0',
'version' => '2.10.0.0',
'reference' => 'f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38',
'type' => 'library',
'install_path' => __DIR__ . '/../react/promise',
'aliases' => array(),
'dev_requirement' => false,
),
'rhumsaa/uuid' => array(
'dev_requirement' => false,
'replaced' => array(
0 => '3.9.7',
),
),
'simshaun/recurr' => array(
'pretty_version' => 'v5.0.1',
'version' => '5.0.1.0',
'reference' => '6887b7bd7075de97c8c69835e0939ff68d23c47e',
'type' => 'library',
'install_path' => __DIR__ . '/../simshaun/recurr',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-ctype' => array(
'pretty_version' => 'v1.27.0',
'version' => '1.27.0.0',
'reference' => '5bbc823adecdae860bb64756d639ecfec17b050a',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
'aliases' => array(),
'dev_requirement' => false,
),
'webmozart/assert' => array(
'pretty_version' => '1.11.0',
'version' => '1.11.0.0',
'reference' => '11cb2199493b2f8a3b53e7f19068fc6aac760991',
'type' => 'library',
'install_path' => __DIR__ . '/../webmozart/assert',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

26
vendor/composer/platform_check.php vendored Normal file
View File

@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 70200)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

View File

@ -0,0 +1,32 @@
{
"active": true,
"name": "Collections",
"slug": "collections",
"docsSlug": "doctrine-collections",
"versions": [
{
"name": "2.0",
"branchName": "2.0.x",
"slug": "latest",
"upcoming": true
},
{
"name": "1.8",
"branchName": "1.8.x",
"slug": "1.8",
"upcoming": true
},
{
"name": "1.7",
"branchName": "1.7.x",
"slug": "1.7",
"current": true
},
{
"name": "1.6",
"branchName": "1.6.x",
"slug": "1.6",
"maintained": false
}
]
}

19
vendor/doctrine/collections/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2006-2013 Doctrine Project
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.

View File

@ -0,0 +1,61 @@
{
"name": "doctrine/collections",
"description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.",
"license": "MIT",
"type": "library",
"keywords": [
"php",
"collections",
"array",
"iterators"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
}
],
"homepage": "https://www.doctrine-project.org/projects/collections.html",
"require": {
"php": "^7.1.3 || ^8.0",
"doctrine/deprecations": "^0.5.3 || ^1"
},
"require-dev": {
"doctrine/coding-standard": "^9.0 || ^10.0",
"phpstan/phpstan": "^1.4.8",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5",
"vimeo/psalm": "^4.22"
},
"autoload": {
"psr-4": {
"Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections"
}
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Tests\\": "tests/Doctrine/Tests"
}
},
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}

View File

@ -0,0 +1,389 @@
<?php
namespace Doctrine\Common\Collections;
use Closure;
use LogicException;
use ReturnTypeWillChange;
use Traversable;
/**
* Lazy collection that is backed by a concrete collection
*
* @psalm-template TKey of array-key
* @psalm-template T
* @template-implements Collection<TKey,T>
*/
abstract class AbstractLazyCollection implements Collection
{
/**
* The backed collection to use
*
* @psalm-var Collection<TKey,T>|null
* @var Collection<mixed>|null
*/
protected $collection;
/** @var bool */
protected $initialized = false;
/**
* {@inheritDoc}
*
* @return int
*/
#[ReturnTypeWillChange]
public function count()
{
$this->initialize();
return $this->collection->count();
}
/**
* {@inheritDoc}
*/
public function add($element)
{
$this->initialize();
return $this->collection->add($element);
}
/**
* {@inheritDoc}
*/
public function clear()
{
$this->initialize();
$this->collection->clear();
}
/**
* {@inheritDoc}
*
* @template TMaybeContained
*/
public function contains($element)
{
$this->initialize();
return $this->collection->contains($element);
}
/**
* {@inheritDoc}
*/
public function isEmpty()
{
$this->initialize();
return $this->collection->isEmpty();
}
/**
* {@inheritDoc}
*/
public function remove($key)
{
$this->initialize();
return $this->collection->remove($key);
}
/**
* {@inheritDoc}
*/
public function removeElement($element)
{
$this->initialize();
return $this->collection->removeElement($element);
}
/**
* {@inheritDoc}
*/
public function containsKey($key)
{
$this->initialize();
return $this->collection->containsKey($key);
}
/**
* {@inheritDoc}
*/
public function get($key)
{
$this->initialize();
return $this->collection->get($key);
}
/**
* {@inheritDoc}
*/
public function getKeys()
{
$this->initialize();
return $this->collection->getKeys();
}
/**
* {@inheritDoc}
*/
public function getValues()
{
$this->initialize();
return $this->collection->getValues();
}
/**
* {@inheritDoc}
*/
public function set($key, $value)
{
$this->initialize();
$this->collection->set($key, $value);
}
/**
* {@inheritDoc}
*/
public function toArray()
{
$this->initialize();
return $this->collection->toArray();
}
/**
* {@inheritDoc}
*/
public function first()
{
$this->initialize();
return $this->collection->first();
}
/**
* {@inheritDoc}
*/
public function last()
{
$this->initialize();
return $this->collection->last();
}
/**
* {@inheritDoc}
*/
public function key()
{
$this->initialize();
return $this->collection->key();
}
/**
* {@inheritDoc}
*/
public function current()
{
$this->initialize();
return $this->collection->current();
}
/**
* {@inheritDoc}
*/
public function next()
{
$this->initialize();
return $this->collection->next();
}
/**
* {@inheritDoc}
*/
public function exists(Closure $p)
{
$this->initialize();
return $this->collection->exists($p);
}
/**
* {@inheritDoc}
*/
public function filter(Closure $p)
{
$this->initialize();
return $this->collection->filter($p);
}
/**
* {@inheritDoc}
*/
public function forAll(Closure $p)
{
$this->initialize();
return $this->collection->forAll($p);
}
/**
* {@inheritDoc}
*/
public function map(Closure $func)
{
$this->initialize();
return $this->collection->map($func);
}
/**
* {@inheritDoc}
*/
public function partition(Closure $p)
{
$this->initialize();
return $this->collection->partition($p);
}
/**
* {@inheritDoc}
*
* @template TMaybeContained
*/
public function indexOf($element)
{
$this->initialize();
return $this->collection->indexOf($element);
}
/**
* {@inheritDoc}
*/
public function slice($offset, $length = null)
{
$this->initialize();
return $this->collection->slice($offset, $length);
}
/**
* {@inheritDoc}
*
* @return Traversable<int|string, mixed>
* @psalm-return Traversable<TKey,T>
*/
#[ReturnTypeWillChange]
public function getIterator()
{
$this->initialize();
return $this->collection->getIterator();
}
/**
* @param TKey $offset
*
* @return bool
*/
#[ReturnTypeWillChange]
public function offsetExists($offset)
{
$this->initialize();
return $this->collection->offsetExists($offset);
}
/**
* @param TKey $offset
*
* @return mixed
*/
#[ReturnTypeWillChange]
public function offsetGet($offset)
{
$this->initialize();
return $this->collection->offsetGet($offset);
}
/**
* @param TKey|null $offset
* @param T $value
*
* @return void
*/
#[ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
$this->initialize();
$this->collection->offsetSet($offset, $value);
}
/**
* @param TKey $offset
*
* @return void
*/
#[ReturnTypeWillChange]
public function offsetUnset($offset)
{
$this->initialize();
$this->collection->offsetUnset($offset);
}
/**
* Is the lazy collection already initialized?
*
* @return bool
*
* @psalm-assert-if-true Collection<TKey,T> $this->collection
*/
public function isInitialized()
{
return $this->initialized;
}
/**
* Initialize the collection
*
* @return void
*
* @psalm-assert Collection<TKey,T> $this->collection
*/
protected function initialize()
{
if ($this->initialized) {
return;
}
$this->doInitialize();
$this->initialized = true;
if ($this->collection === null) {
throw new LogicException('You must initialize the collection property in the doInitialize() method.');
}
}
/**
* Do the initialization logic
*
* @return void
*/
abstract protected function doInitialize();
}

View File

@ -0,0 +1,466 @@
<?php
namespace Doctrine\Common\Collections;
use ArrayIterator;
use Closure;
use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor;
use ReturnTypeWillChange;
use Traversable;
use function array_filter;
use function array_key_exists;
use function array_keys;
use function array_map;
use function array_reverse;
use function array_search;
use function array_slice;
use function array_values;
use function count;
use function current;
use function end;
use function in_array;
use function key;
use function next;
use function reset;
use function spl_object_hash;
use function uasort;
use const ARRAY_FILTER_USE_BOTH;
/**
* An ArrayCollection is a Collection implementation that wraps a regular PHP array.
*
* Warning: Using (un-)serialize() on a collection is not a supported use-case
* and may break when we change the internals in the future. If you need to
* serialize a collection use {@link toArray()} and reconstruct the collection
* manually.
*
* @psalm-template TKey of array-key
* @psalm-template T
* @template-implements Collection<TKey,T>
* @template-implements Selectable<TKey,T>
* @psalm-consistent-constructor
*/
class ArrayCollection implements Collection, Selectable
{
/**
* An array containing the entries of this collection.
*
* @psalm-var array<TKey,T>
* @var mixed[]
*/
private $elements;
/**
* Initializes a new ArrayCollection.
*
* @param array $elements
* @psalm-param array<TKey,T> $elements
*/
public function __construct(array $elements = [])
{
$this->elements = $elements;
}
/**
* {@inheritDoc}
*/
public function toArray()
{
return $this->elements;
}
/**
* {@inheritDoc}
*/
public function first()
{
return reset($this->elements);
}
/**
* Creates a new instance from the specified elements.
*
* This method is provided for derived classes to specify how a new
* instance should be created when constructor semantics have changed.
*
* @param array $elements Elements.
* @psalm-param array<K,V> $elements
*
* @return static
* @psalm-return static<K,V>
*
* @psalm-template K of array-key
* @psalm-template V
*/
protected function createFrom(array $elements)
{
return new static($elements);
}
/**
* {@inheritDoc}
*/
public function last()
{
return end($this->elements);
}
/**
* {@inheritDoc}
*/
public function key()
{
return key($this->elements);
}
/**
* {@inheritDoc}
*/
public function next()
{
return next($this->elements);
}
/**
* {@inheritDoc}
*/
public function current()
{
return current($this->elements);
}
/**
* {@inheritDoc}
*/
public function remove($key)
{
if (! isset($this->elements[$key]) && ! array_key_exists($key, $this->elements)) {
return null;
}
$removed = $this->elements[$key];
unset($this->elements[$key]);
return $removed;
}
/**
* {@inheritDoc}
*/
public function removeElement($element)
{
$key = array_search($element, $this->elements, true);
if ($key === false) {
return false;
}
unset($this->elements[$key]);
return true;
}
/**
* Required by interface ArrayAccess.
*
* @param TKey $offset
*
* @return bool
*/
#[ReturnTypeWillChange]
public function offsetExists($offset)
{
return $this->containsKey($offset);
}
/**
* Required by interface ArrayAccess.
*
* @param TKey $offset
*
* @return mixed
*/
#[ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
* Required by interface ArrayAccess.
*
* @param TKey|null $offset
* @param T $value
*
* @return void
*/
#[ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
if (! isset($offset)) {
$this->add($value);
return;
}
$this->set($offset, $value);
}
/**
* Required by interface ArrayAccess.
*
* @param TKey $offset
*
* @return void
*/
#[ReturnTypeWillChange]
public function offsetUnset($offset)
{
$this->remove($offset);
}
/**
* {@inheritDoc}
*/
public function containsKey($key)
{
return isset($this->elements[$key]) || array_key_exists($key, $this->elements);
}
/**
* {@inheritDoc}
*
* @template TMaybeContained
*/
public function contains($element)
{
return in_array($element, $this->elements, true);
}
/**
* {@inheritDoc}
*/
public function exists(Closure $p)
{
foreach ($this->elements as $key => $element) {
if ($p($key, $element)) {
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*
* @psalm-param TMaybeContained $element
*
* @psalm-return (TMaybeContained is T ? TKey|false : false)
*
* @template TMaybeContained
*/
public function indexOf($element)
{
return array_search($element, $this->elements, true);
}
/**
* {@inheritDoc}
*/
public function get($key)
{
return $this->elements[$key] ?? null;
}
/**
* {@inheritDoc}
*/
public function getKeys()
{
return array_keys($this->elements);
}
/**
* {@inheritDoc}
*/
public function getValues()
{
return array_values($this->elements);
}
/**
* {@inheritDoc}
*
* @return int
*/
#[ReturnTypeWillChange]
public function count()
{
return count($this->elements);
}
/**
* {@inheritDoc}
*/
public function set($key, $value)
{
$this->elements[$key] = $value;
}
/**
* {@inheritDoc}
*
* @psalm-suppress InvalidPropertyAssignmentValue
*
* This breaks assumptions about the template type, but it would
* be a backwards-incompatible change to remove this method
*/
public function add($element)
{
$this->elements[] = $element;
return true;
}
/**
* {@inheritDoc}
*/
public function isEmpty()
{
return empty($this->elements);
}
/**
* {@inheritDoc}
*
* @return Traversable<int|string, mixed>
* @psalm-return Traversable<TKey,T>
*/
#[ReturnTypeWillChange]
public function getIterator()
{
return new ArrayIterator($this->elements);
}
/**
* {@inheritDoc}
*
* @psalm-param Closure(T):U $func
*
* @return static
* @psalm-return static<TKey, U>
*
* @psalm-template U
*/
public function map(Closure $func)
{
return $this->createFrom(array_map($func, $this->elements));
}
/**
* {@inheritDoc}
*
* @return static
* @psalm-return static<TKey,T>
*/
public function filter(Closure $p)
{
return $this->createFrom(array_filter($this->elements, $p, ARRAY_FILTER_USE_BOTH));
}
/**
* {@inheritDoc}
*/
public function forAll(Closure $p)
{
foreach ($this->elements as $key => $element) {
if (! $p($key, $element)) {
return false;
}
}
return true;
}
/**
* {@inheritDoc}
*/
public function partition(Closure $p)
{
$matches = $noMatches = [];
foreach ($this->elements as $key => $element) {
if ($p($key, $element)) {
$matches[$key] = $element;
} else {
$noMatches[$key] = $element;
}
}
return [$this->createFrom($matches), $this->createFrom($noMatches)];
}
/**
* Returns a string representation of this object.
*
* @return string
*/
public function __toString()
{
return self::class . '@' . spl_object_hash($this);
}
/**
* {@inheritDoc}
*/
public function clear()
{
$this->elements = [];
}
/**
* {@inheritDoc}
*/
public function slice($offset, $length = null)
{
return array_slice($this->elements, $offset, $length, true);
}
/**
* {@inheritDoc}
*/
public function matching(Criteria $criteria)
{
$expr = $criteria->getWhereExpression();
$filtered = $this->elements;
if ($expr) {
$visitor = new ClosureExpressionVisitor();
$filter = $visitor->dispatch($expr);
$filtered = array_filter($filtered, $filter);
}
$orderings = $criteria->getOrderings();
if ($orderings) {
$next = null;
foreach (array_reverse($orderings) as $field => $ordering) {
$next = ClosureExpressionVisitor::sortByField($field, $ordering === Criteria::DESC ? -1 : 1, $next);
}
uasort($filtered, $next);
}
$offset = $criteria->getFirstResult();
$length = $criteria->getMaxResults();
if ($offset || $length) {
$filtered = array_slice($filtered, (int) $offset, $length);
}
return $this->createFrom($filtered);
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace Doctrine\Common\Collections;
use ArrayAccess;
use Closure;
/**
* The missing (SPL) Collection/Array/OrderedMap interface.
*
* A Collection resembles the nature of a regular PHP array. That is,
* it is essentially an <b>ordered map</b> that can also be used
* like a list.
*
* A Collection has an internal iterator just like a PHP array. In addition,
* a Collection can be iterated with external iterators, which is preferable.
* To use an external iterator simply use the foreach language construct to
* iterate over the collection (which calls {@link getIterator()} internally) or
* explicitly retrieve an iterator though {@link getIterator()} which can then be
* used to iterate over the collection.
* You can not rely on the internal iterator of the collection being at a certain
* position unless you explicitly positioned it before. Prefer iteration with
* external iterators.
*
* @psalm-template TKey of array-key
* @psalm-template T
* @template-extends ReadableCollection<TKey, T>
* @template-extends ArrayAccess<TKey, T>
*/
interface Collection extends ReadableCollection, ArrayAccess
{
/**
* Adds an element at the end of the collection.
*
* @param mixed $element The element to add.
* @psalm-param T $element
*
* @return true Always TRUE.
*/
public function add($element);
/**
* Clears the collection, removing all elements.
*
* @return void
*/
public function clear();
/**
* Removes the element at the specified index from the collection.
*
* @param string|int $key The key/index of the element to remove.
* @psalm-param TKey $key
*
* @return mixed The removed element or NULL, if the collection did not contain the element.
* @psalm-return T|null
*/
public function remove($key);
/**
* Removes the specified element from the collection, if it is found.
*
* @param mixed $element The element to remove.
* @psalm-param T $element
*
* @return bool TRUE if this collection contained the specified element, FALSE otherwise.
*/
public function removeElement($element);
/**
* Sets an element in the collection at the specified key/index.
*
* @param string|int $key The key/index of the element to set.
* @param mixed $value The element to set.
* @psalm-param TKey $key
* @psalm-param T $value
*
* @return void
*/
public function set($key, $value);
/**
* {@inheritdoc}
*
* @return Collection<mixed> A collection with the results of the filter operation.
* @psalm-return Collection<TKey, T>
*/
public function filter(Closure $p);
/**
* {@inheritdoc}
*
* @return Collection<mixed>[] An array with two elements. The first element contains the collection
* of elements where the predicate returned TRUE, the second element
* contains the collection of elements where the predicate returned FALSE.
* @psalm-return array{0: Collection<TKey, T>, 1: Collection<TKey, T>}
*/
public function partition(Closure $p);
}

View File

@ -0,0 +1,245 @@
<?php
namespace Doctrine\Common\Collections;
use Doctrine\Common\Collections\Expr\CompositeExpression;
use Doctrine\Common\Collections\Expr\Expression;
use Doctrine\Deprecations\Deprecation;
use function array_map;
use function func_num_args;
use function strtoupper;
/**
* Criteria for filtering Selectable collections.
*
* @psalm-consistent-constructor
*/
class Criteria
{
public const ASC = 'ASC';
public const DESC = 'DESC';
/** @var ExpressionBuilder|null */
private static $expressionBuilder;
/** @var Expression|null */
private $expression;
/** @var string[] */
private $orderings = [];
/** @var int|null */
private $firstResult;
/** @var int|null */
private $maxResults;
/**
* Creates an instance of the class.
*
* @return Criteria
*/
public static function create()
{
return new static();
}
/**
* Returns the expression builder.
*
* @return ExpressionBuilder
*/
public static function expr()
{
if (self::$expressionBuilder === null) {
self::$expressionBuilder = new ExpressionBuilder();
}
return self::$expressionBuilder;
}
/**
* Construct a new Criteria.
*
* @param string[]|null $orderings
* @param int|null $firstResult
* @param int|null $maxResults
*/
public function __construct(?Expression $expression = null, ?array $orderings = null, $firstResult = null, $maxResults = null)
{
$this->expression = $expression;
if ($firstResult === null && func_num_args() > 2) {
Deprecation::trigger(
'doctrine/collections',
'https://github.com/doctrine/collections/pull/311',
'Passing null as $firstResult to the constructor of %s is deprecated. Pass 0 instead or omit the argument.',
self::class
);
}
$this->setFirstResult($firstResult);
$this->setMaxResults($maxResults);
if ($orderings === null) {
return;
}
$this->orderBy($orderings);
}
/**
* Sets the where expression to evaluate when this Criteria is searched for.
*
* @return $this
*/
public function where(Expression $expression)
{
$this->expression = $expression;
return $this;
}
/**
* Appends the where expression to evaluate when this Criteria is searched for
* using an AND with previous expression.
*
* @return $this
*/
public function andWhere(Expression $expression)
{
if ($this->expression === null) {
return $this->where($expression);
}
$this->expression = new CompositeExpression(
CompositeExpression::TYPE_AND,
[$this->expression, $expression]
);
return $this;
}
/**
* Appends the where expression to evaluate when this Criteria is searched for
* using an OR with previous expression.
*
* @return $this
*/
public function orWhere(Expression $expression)
{
if ($this->expression === null) {
return $this->where($expression);
}
$this->expression = new CompositeExpression(
CompositeExpression::TYPE_OR,
[$this->expression, $expression]
);
return $this;
}
/**
* Gets the expression attached to this Criteria.
*
* @return Expression|null
*/
public function getWhereExpression()
{
return $this->expression;
}
/**
* Gets the current orderings of this Criteria.
*
* @return string[]
*/
public function getOrderings()
{
return $this->orderings;
}
/**
* Sets the ordering of the result of this Criteria.
*
* Keys are field and values are the order, being either ASC or DESC.
*
* @see Criteria::ASC
* @see Criteria::DESC
*
* @param string[] $orderings
*
* @return $this
*/
public function orderBy(array $orderings)
{
$this->orderings = array_map(
static function (string $ordering): string {
return strtoupper($ordering) === Criteria::ASC ? Criteria::ASC : Criteria::DESC;
},
$orderings
);
return $this;
}
/**
* Gets the current first result option of this Criteria.
*
* @return int|null
*/
public function getFirstResult()
{
return $this->firstResult;
}
/**
* Set the number of first result that this Criteria should return.
*
* @param int|null $firstResult The value to set.
*
* @return $this
*/
public function setFirstResult($firstResult)
{
if ($firstResult === null) {
Deprecation::triggerIfCalledFromOutside(
'doctrine/collections',
'https://github.com/doctrine/collections/pull/311',
'Passing null to %s() is deprecated, pass 0 instead.',
__METHOD__
);
}
$this->firstResult = $firstResult;
return $this;
}
/**
* Gets maxResults.
*
* @return int|null
*/
public function getMaxResults()
{
return $this->maxResults;
}
/**
* Sets maxResults.
*
* @param int|null $maxResults The value to set.
*
* @return $this
*/
public function setMaxResults($maxResults)
{
$this->maxResults = $maxResults;
return $this;
}
}

View File

@ -0,0 +1,269 @@
<?php
namespace Doctrine\Common\Collections\Expr;
use ArrayAccess;
use Closure;
use RuntimeException;
use function explode;
use function in_array;
use function is_array;
use function is_scalar;
use function iterator_to_array;
use function method_exists;
use function preg_match;
use function preg_replace_callback;
use function strlen;
use function strpos;
use function strtoupper;
use function substr;
/**
* Walks an expression graph and turns it into a PHP closure.
*
* This closure can be used with {@Collection#filter()} and is used internally
* by {@ArrayCollection#select()}.
*/
class ClosureExpressionVisitor extends ExpressionVisitor
{
/**
* Accesses the field of a given object. This field has to be public
* directly or indirectly (through an accessor get*, is*, or a magic
* method, __get, __call).
*
* @param object|mixed[] $object
* @param string $field
*
* @return mixed
*/
public static function getObjectFieldValue($object, $field)
{
if (strpos($field, '.') !== false) {
[$field, $subField] = explode('.', $field, 2);
$object = self::getObjectFieldValue($object, $field);
return self::getObjectFieldValue($object, $subField);
}
if (is_array($object)) {
return $object[$field];
}
$accessors = ['get', 'is', ''];
foreach ($accessors as $accessor) {
$accessor .= $field;
if (method_exists($object, $accessor)) {
return $object->$accessor();
}
}
if (preg_match('/^is[A-Z]+/', $field) === 1 && method_exists($object, $field)) {
return $object->$field();
}
// __call should be triggered for get.
$accessor = $accessors[0] . $field;
if (method_exists($object, '__call')) {
return $object->$accessor();
}
if ($object instanceof ArrayAccess) {
return $object[$field];
}
if (isset($object->$field)) {
return $object->$field;
}
// camelcase field name to support different variable naming conventions
$ccField = preg_replace_callback('/_(.?)/', static function ($matches) {
return strtoupper($matches[1]);
}, $field);
foreach ($accessors as $accessor) {
$accessor .= $ccField;
if (method_exists($object, $accessor)) {
return $object->$accessor();
}
}
return $object->$field;
}
/**
* Helper for sorting arrays of objects based on multiple fields + orientations.
*
* @param string $name
* @param int $orientation
*
* @return Closure
*/
public static function sortByField($name, $orientation = 1, ?Closure $next = null)
{
if (! $next) {
$next = static function (): int {
return 0;
};
}
return static function ($a, $b) use ($name, $next, $orientation): int {
$aValue = ClosureExpressionVisitor::getObjectFieldValue($a, $name);
$bValue = ClosureExpressionVisitor::getObjectFieldValue($b, $name);
if ($aValue === $bValue) {
return $next($a, $b);
}
return ($aValue > $bValue ? 1 : -1) * $orientation;
};
}
/**
* {@inheritDoc}
*/
public function walkComparison(Comparison $comparison)
{
$field = $comparison->getField();
$value = $comparison->getValue()->getValue(); // shortcut for walkValue()
switch ($comparison->getOperator()) {
case Comparison::EQ:
return static function ($object) use ($field, $value): bool {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) === $value;
};
case Comparison::NEQ:
return static function ($object) use ($field, $value): bool {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) !== $value;
};
case Comparison::LT:
return static function ($object) use ($field, $value): bool {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) < $value;
};
case Comparison::LTE:
return static function ($object) use ($field, $value): bool {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) <= $value;
};
case Comparison::GT:
return static function ($object) use ($field, $value): bool {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) > $value;
};
case Comparison::GTE:
return static function ($object) use ($field, $value): bool {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) >= $value;
};
case Comparison::IN:
return static function ($object) use ($field, $value): bool {
$fieldValue = ClosureExpressionVisitor::getObjectFieldValue($object, $field);
return in_array($fieldValue, $value, is_scalar($fieldValue));
};
case Comparison::NIN:
return static function ($object) use ($field, $value): bool {
$fieldValue = ClosureExpressionVisitor::getObjectFieldValue($object, $field);
return ! in_array($fieldValue, $value, is_scalar($fieldValue));
};
case Comparison::CONTAINS:
return static function ($object) use ($field, $value) {
return strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value) !== false;
};
case Comparison::MEMBER_OF:
return static function ($object) use ($field, $value): bool {
$fieldValues = ClosureExpressionVisitor::getObjectFieldValue($object, $field);
if (! is_array($fieldValues)) {
$fieldValues = iterator_to_array($fieldValues);
}
return in_array($value, $fieldValues, true);
};
case Comparison::STARTS_WITH:
return static function ($object) use ($field, $value): bool {
return strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value) === 0;
};
case Comparison::ENDS_WITH:
return static function ($object) use ($field, $value): bool {
return $value === substr(ClosureExpressionVisitor::getObjectFieldValue($object, $field), -strlen($value));
};
default:
throw new RuntimeException('Unknown comparison operator: ' . $comparison->getOperator());
}
}
/**
* {@inheritDoc}
*/
public function walkValue(Value $value)
{
return $value->getValue();
}
/**
* {@inheritDoc}
*/
public function walkCompositeExpression(CompositeExpression $expr)
{
$expressionList = [];
foreach ($expr->getExpressionList() as $child) {
$expressionList[] = $this->dispatch($child);
}
switch ($expr->getType()) {
case CompositeExpression::TYPE_AND:
return $this->andExpressions($expressionList);
case CompositeExpression::TYPE_OR:
return $this->orExpressions($expressionList);
default:
throw new RuntimeException('Unknown composite ' . $expr->getType());
}
}
/** @param callable[] $expressions */
private function andExpressions(array $expressions): callable
{
return static function ($object) use ($expressions): bool {
foreach ($expressions as $expression) {
if (! $expression($object)) {
return false;
}
}
return true;
};
}
/** @param callable[] $expressions */
private function orExpressions(array $expressions): callable
{
return static function ($object) use ($expressions): bool {
foreach ($expressions as $expression) {
if ($expression($object)) {
return true;
}
}
return false;
};
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace Doctrine\Common\Collections\Expr;
/**
* Comparison of a field with a value by the given operator.
*/
class Comparison implements Expression
{
public const EQ = '=';
public const NEQ = '<>';
public const LT = '<';
public const LTE = '<=';
public const GT = '>';
public const GTE = '>=';
public const IS = '='; // no difference with EQ
public const IN = 'IN';
public const NIN = 'NIN';
public const CONTAINS = 'CONTAINS';
public const MEMBER_OF = 'MEMBER_OF';
public const STARTS_WITH = 'STARTS_WITH';
public const ENDS_WITH = 'ENDS_WITH';
/** @var string */
private $field;
/** @var string */
private $op;
/** @var Value */
private $value;
/**
* @param string $field
* @param string $operator
* @param mixed $value
*/
public function __construct($field, $operator, $value)
{
if (! ($value instanceof Value)) {
$value = new Value($value);
}
$this->field = $field;
$this->op = $operator;
$this->value = $value;
}
/** @return string */
public function getField()
{
return $this->field;
}
/** @return Value */
public function getValue()
{
return $this->value;
}
/** @return string */
public function getOperator()
{
return $this->op;
}
/**
* {@inheritDoc}
*/
public function visit(ExpressionVisitor $visitor)
{
return $visitor->walkComparison($this);
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace Doctrine\Common\Collections\Expr;
use RuntimeException;
/**
* Expression of Expressions combined by AND or OR operation.
*/
class CompositeExpression implements Expression
{
public const TYPE_AND = 'AND';
public const TYPE_OR = 'OR';
/** @var string */
private $type;
/** @var Expression[] */
private $expressions = [];
/**
* @param string $type
* @param mixed[] $expressions
*
* @throws RuntimeException
*/
public function __construct($type, array $expressions)
{
$this->type = $type;
foreach ($expressions as $expr) {
if ($expr instanceof Value) {
throw new RuntimeException('Values are not supported expressions as children of and/or expressions.');
}
if (! ($expr instanceof Expression)) {
throw new RuntimeException('No expression given to CompositeExpression.');
}
$this->expressions[] = $expr;
}
}
/**
* Returns the list of expressions nested in this composite.
*
* @return Expression[]
*/
public function getExpressionList()
{
return $this->expressions;
}
/** @return string */
public function getType()
{
return $this->type;
}
/**
* {@inheritDoc}
*/
public function visit(ExpressionVisitor $visitor)
{
return $visitor->walkCompositeExpression($this);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Doctrine\Common\Collections\Expr;
/**
* Expression for the {@link Selectable} interface.
*/
interface Expression
{
/** @return mixed */
public function visit(ExpressionVisitor $visitor);
}

View File

@ -0,0 +1,59 @@
<?php
namespace Doctrine\Common\Collections\Expr;
use RuntimeException;
use function get_class;
/**
* An Expression visitor walks a graph of expressions and turns them into a
* query for the underlying implementation.
*/
abstract class ExpressionVisitor
{
/**
* Converts a comparison expression into the target query language output.
*
* @return mixed
*/
abstract public function walkComparison(Comparison $comparison);
/**
* Converts a value expression into the target query language part.
*
* @return mixed
*/
abstract public function walkValue(Value $value);
/**
* Converts a composite expression into the target query language output.
*
* @return mixed
*/
abstract public function walkCompositeExpression(CompositeExpression $expr);
/**
* Dispatches walking an expression to the appropriate handler.
*
* @return mixed
*
* @throws RuntimeException
*/
public function dispatch(Expression $expr)
{
switch (true) {
case $expr instanceof Comparison:
return $this->walkComparison($expr);
case $expr instanceof Value:
return $this->walkValue($expr);
case $expr instanceof CompositeExpression:
return $this->walkCompositeExpression($expr);
default:
throw new RuntimeException('Unknown Expression ' . get_class($expr));
}
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Doctrine\Common\Collections\Expr;
class Value implements Expression
{
/** @var mixed */
private $value;
/** @param mixed $value */
public function __construct($value)
{
$this->value = $value;
}
/** @return mixed */
public function getValue()
{
return $this->value;
}
/**
* {@inheritDoc}
*/
public function visit(ExpressionVisitor $visitor)
{
return $visitor->walkValue($this);
}
}

View File

@ -0,0 +1,181 @@
<?php
namespace Doctrine\Common\Collections;
use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\Common\Collections\Expr\CompositeExpression;
use Doctrine\Common\Collections\Expr\Value;
use function func_get_args;
/**
* Builder for Expressions in the {@link Selectable} interface.
*
* Important Notice for interoperable code: You have to use scalar
* values only for comparisons, otherwise the behavior of the comparison
* may be different between implementations (Array vs ORM vs ODM).
*/
class ExpressionBuilder
{
/**
* @param mixed ...$x
*
* @return CompositeExpression
*/
public function andX($x = null)
{
return new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args());
}
/**
* @param mixed ...$x
*
* @return CompositeExpression
*/
public function orX($x = null)
{
return new CompositeExpression(CompositeExpression::TYPE_OR, func_get_args());
}
/**
* @param string $field
* @param mixed $value
*
* @return Comparison
*/
public function eq($field, $value)
{
return new Comparison($field, Comparison::EQ, new Value($value));
}
/**
* @param string $field
* @param mixed $value
*
* @return Comparison
*/
public function gt($field, $value)
{
return new Comparison($field, Comparison::GT, new Value($value));
}
/**
* @param string $field
* @param mixed $value
*
* @return Comparison
*/
public function lt($field, $value)
{
return new Comparison($field, Comparison::LT, new Value($value));
}
/**
* @param string $field
* @param mixed $value
*
* @return Comparison
*/
public function gte($field, $value)
{
return new Comparison($field, Comparison::GTE, new Value($value));
}
/**
* @param string $field
* @param mixed $value
*
* @return Comparison
*/
public function lte($field, $value)
{
return new Comparison($field, Comparison::LTE, new Value($value));
}
/**
* @param string $field
* @param mixed $value
*
* @return Comparison
*/
public function neq($field, $value)
{
return new Comparison($field, Comparison::NEQ, new Value($value));
}
/**
* @param string $field
*
* @return Comparison
*/
public function isNull($field)
{
return new Comparison($field, Comparison::EQ, new Value(null));
}
/**
* @param string $field
* @param mixed[] $values
*
* @return Comparison
*/
public function in($field, array $values)
{
return new Comparison($field, Comparison::IN, new Value($values));
}
/**
* @param string $field
* @param mixed[] $values
*
* @return Comparison
*/
public function notIn($field, array $values)
{
return new Comparison($field, Comparison::NIN, new Value($values));
}
/**
* @param string $field
* @param mixed $value
*
* @return Comparison
*/
public function contains($field, $value)
{
return new Comparison($field, Comparison::CONTAINS, new Value($value));
}
/**
* @param string $field
* @param mixed $value
*
* @return Comparison
*/
public function memberOf($field, $value)
{
return new Comparison($field, Comparison::MEMBER_OF, new Value($value));
}
/**
* @param string $field
* @param mixed $value
*
* @return Comparison
*/
public function startsWith($field, $value)
{
return new Comparison($field, Comparison::STARTS_WITH, new Value($value));
}
/**
* @param string $field
* @param mixed $value
*
* @return Comparison
*/
public function endsWith($field, $value)
{
return new Comparison($field, Comparison::ENDS_WITH, new Value($value));
}
}

View File

@ -0,0 +1,213 @@
<?php
namespace Doctrine\Common\Collections;
use Closure;
use Countable;
use IteratorAggregate;
/**
* @psalm-template TKey of array-key
* @template-covariant T
* @template-extends IteratorAggregate<TKey, T>
*/
interface ReadableCollection extends Countable, IteratorAggregate
{
/**
* Checks whether an element is contained in the collection.
* This is an O(n) operation, where n is the size of the collection.
*
* @param mixed $element The element to search for.
* @psalm-param TMaybeContained $element
*
* @return bool TRUE if the collection contains the element, FALSE otherwise.
* @psalm-return (TMaybeContained is T ? bool : false)
*
* @template TMaybeContained
*/
public function contains($element);
/**
* Checks whether the collection is empty (contains no elements).
*
* @return bool TRUE if the collection is empty, FALSE otherwise.
*/
public function isEmpty();
/**
* Checks whether the collection contains an element with the specified key/index.
*
* @param string|int $key The key/index to check for.
* @psalm-param TKey $key
*
* @return bool TRUE if the collection contains an element with the specified key/index,
* FALSE otherwise.
*/
public function containsKey($key);
/**
* Gets the element at the specified key/index.
*
* @param string|int $key The key/index of the element to retrieve.
* @psalm-param TKey $key
*
* @return mixed
* @psalm-return T|null
*/
public function get($key);
/**
* Gets all keys/indices of the collection.
*
* @return int[]|string[] The keys/indices of the collection, in the order of the corresponding
* elements in the collection.
* @psalm-return list<TKey>
*/
public function getKeys();
/**
* Gets all values of the collection.
*
* @return mixed[] The values of all elements in the collection, in the
* order they appear in the collection.
* @psalm-return list<T>
*/
public function getValues();
/**
* Gets a native PHP array representation of the collection.
*
* @return mixed[]
* @psalm-return array<TKey,T>
*/
public function toArray();
/**
* Sets the internal iterator to the first element in the collection and returns this element.
*
* @return mixed
* @psalm-return T|false
*/
public function first();
/**
* Sets the internal iterator to the last element in the collection and returns this element.
*
* @return mixed
* @psalm-return T|false
*/
public function last();
/**
* Gets the key/index of the element at the current iterator position.
*
* @return int|string|null
* @psalm-return TKey|null
*/
public function key();
/**
* Gets the element of the collection at the current iterator position.
*
* @return mixed
* @psalm-return T|false
*/
public function current();
/**
* Moves the internal iterator position to the next element and returns this element.
*
* @return mixed
* @psalm-return T|false
*/
public function next();
/**
* Extracts a slice of $length elements starting at position $offset from the Collection.
*
* If $length is null it returns all elements from $offset to the end of the Collection.
* Keys have to be preserved by this method. Calling this method will only return the
* selected slice and NOT change the elements contained in the collection slice is called on.
*
* @param int $offset The offset to start from.
* @param int|null $length The maximum number of elements to return, or null for no limit.
*
* @return mixed[]
* @psalm-return array<TKey,T>
*/
public function slice($offset, $length = null);
/**
* Tests for the existence of an element that satisfies the given predicate.
*
* @param Closure $p The predicate.
* @psalm-param Closure(TKey, T):bool $p
*
* @return bool TRUE if the predicate is TRUE for at least one element, FALSE otherwise.
*/
public function exists(Closure $p);
/**
* Returns all the elements of this collection that satisfy the predicate p.
* The order of the elements is preserved.
*
* @param Closure $p The predicate used for filtering.
* @psalm-param Closure(T):bool $p
*
* @return ReadableCollection<mixed> A collection with the results of the filter operation.
* @psalm-return ReadableCollection<TKey, T>
*/
public function filter(Closure $p);
/**
* Applies the given function to each element in the collection and returns
* a new collection with the elements returned by the function.
*
* @psalm-param Closure(T):U $func
*
* @return Collection<mixed>
* @psalm-return Collection<TKey, U>
*
* @psalm-template U
*/
public function map(Closure $func);
/**
* Partitions this collection in two collections according to a predicate.
* Keys are preserved in the resulting collections.
*
* @param Closure $p The predicate on which to partition.
* @psalm-param Closure(TKey, T):bool $p
*
* @return ReadableCollection<mixed>[] An array with two elements. The first element contains the collection
* of elements where the predicate returned TRUE, the second element
* contains the collection of elements where the predicate returned FALSE.
* @psalm-return array{0: ReadableCollection<TKey, T>, 1: ReadableCollection<TKey, T>}
*/
public function partition(Closure $p);
/**
* Tests whether the given predicate p holds for all elements of this collection.
*
* @param Closure $p The predicate.
* @psalm-param Closure(TKey, T):bool $p
*
* @return bool TRUE, if the predicate yields TRUE for all elements, FALSE otherwise.
*/
public function forAll(Closure $p);
/**
* Gets the index/key of a given element. The comparison of two elements is strict,
* that means not only the value but also the type must match.
* For objects this means reference equality.
*
* @param mixed $element The element to search for.
* @psalm-param TMaybeContained $element
*
* @return int|string|bool The key/index of the element or FALSE if the element was not found.
* @psalm-return (TMaybeContained is T ? TKey|false : false)
*
* @template TMaybeContained
*/
public function indexOf($element);
}

View File

@ -0,0 +1,30 @@
<?php
namespace Doctrine\Common\Collections;
/**
* Interface for collections that allow efficient filtering with an expression API.
*
* Goal of this interface is a backend independent method to fetch elements
* from a collections. {@link Expression} is crafted in a way that you can
* implement queries from both in-memory and database-backed collections.
*
* For database backed collections this allows very efficient access by
* utilizing the query APIs, for example SQL in the ORM. Applications using
* this API can implement efficient database access without having to ask the
* EntityManager or Repositories.
*
* @psalm-template TKey as array-key
* @psalm-template T
*/
interface Selectable
{
/**
* Selects all elements from a selectable that match the expression and
* returns a new collection containing these elements.
*
* @return Collection<mixed>&Selectable<mixed>
* @psalm-return Collection<TKey,T>&Selectable<TKey,T>
*/
public function matching(Criteria $criteria);
}

19
vendor/doctrine/deprecations/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2020-2021 Doctrine Project
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.

View File

@ -0,0 +1,32 @@
{
"name": "doctrine/deprecations",
"type": "library",
"description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
"homepage": "https://www.doctrine-project.org/",
"license": "MIT",
"require": {
"php": "^7.1|^8.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5|^8.5|^9.5",
"psr/log": "^1|^2|^3",
"doctrine/coding-standard": "^9"
},
"suggest": {
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
},
"autoload": {
"psr-4": {"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"}
},
"autoload-dev": {
"psr-4": {
"DeprecationTests\\": "test_fixtures/src",
"Doctrine\\Foo\\": "test_fixtures/vendor/doctrine/foo"
}
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}

View File

@ -0,0 +1,266 @@
<?php
declare(strict_types=1);
namespace Doctrine\Deprecations;
use Psr\Log\LoggerInterface;
use function array_key_exists;
use function array_reduce;
use function debug_backtrace;
use function sprintf;
use function strpos;
use function strrpos;
use function substr;
use function trigger_error;
use const DEBUG_BACKTRACE_IGNORE_ARGS;
use const DIRECTORY_SEPARATOR;
use const E_USER_DEPRECATED;
/**
* Manages Deprecation logging in different ways.
*
* By default triggered exceptions are not logged.
*
* To enable different deprecation logging mechanisms you can call the
* following methods:
*
* - Minimal collection of deprecations via getTriggeredDeprecations()
* \Doctrine\Deprecations\Deprecation::enableTrackingDeprecations();
*
* - Uses @trigger_error with E_USER_DEPRECATED
* \Doctrine\Deprecations\Deprecation::enableWithTriggerError();
*
* - Sends deprecation messages via a PSR-3 logger
* \Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger);
*
* Packages that trigger deprecations should use the `trigger()` or
* `triggerIfCalledFromOutside()` methods.
*/
class Deprecation
{
private const TYPE_NONE = 0;
private const TYPE_TRACK_DEPRECATIONS = 1;
private const TYPE_TRIGGER_ERROR = 2;
private const TYPE_PSR_LOGGER = 4;
/** @var int */
private static $type = self::TYPE_NONE;
/** @var LoggerInterface|null */
private static $logger;
/** @var array<string,bool> */
private static $ignoredPackages = [];
/** @var array<string,int> */
private static $ignoredLinks = [];
/** @var bool */
private static $deduplication = true;
/**
* Trigger a deprecation for the given package and identfier.
*
* The link should point to a Github issue or Wiki entry detailing the
* deprecation. It is additionally used to de-duplicate the trigger of the
* same deprecation during a request.
*
* @param mixed $args
*/
public static function trigger(string $package, string $link, string $message, ...$args): void
{
if (self::$type === self::TYPE_NONE) {
return;
}
if (array_key_exists($link, self::$ignoredLinks)) {
self::$ignoredLinks[$link]++;
} else {
self::$ignoredLinks[$link] = 1;
}
if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) {
return;
}
if (isset(self::$ignoredPackages[$package])) {
return;
}
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$message = sprintf($message, ...$args);
self::delegateTriggerToBackend($message, $backtrace, $link, $package);
}
/**
* Trigger a deprecation for the given package and identifier when called from outside.
*
* "Outside" means we assume that $package is currently installed as a
* dependency and the caller is not a file in that package. When $package
* is installed as a root package then deprecations triggered from the
* tests folder are also considered "outside".
*
* This deprecation method assumes that you are using Composer to install
* the dependency and are using the default /vendor/ folder and not a
* Composer plugin to change the install location. The assumption is also
* that $package is the exact composer packge name.
*
* Compared to {@link trigger()} this method causes some overhead when
* deprecation tracking is enabled even during deduplication, because it
* needs to call {@link debug_backtrace()}
*
* @param mixed $args
*/
public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void
{
if (self::$type === self::TYPE_NONE) {
return;
}
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
// first check that the caller is not from a tests folder, in which case we always let deprecations pass
if (strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) {
$path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package . DIRECTORY_SEPARATOR;
if (strpos($backtrace[0]['file'], $path) === false) {
return;
}
if (strpos($backtrace[1]['file'], $path) !== false) {
return;
}
}
if (array_key_exists($link, self::$ignoredLinks)) {
self::$ignoredLinks[$link]++;
} else {
self::$ignoredLinks[$link] = 1;
}
if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) {
return;
}
if (isset(self::$ignoredPackages[$package])) {
return;
}
$message = sprintf($message, ...$args);
self::delegateTriggerToBackend($message, $backtrace, $link, $package);
}
/**
* @param array<mixed> $backtrace
*/
private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void
{
if ((self::$type & self::TYPE_PSR_LOGGER) > 0) {
$context = [
'file' => $backtrace[0]['file'],
'line' => $backtrace[0]['line'],
'package' => $package,
'link' => $link,
];
self::$logger->notice($message, $context);
}
if (! ((self::$type & self::TYPE_TRIGGER_ERROR) > 0)) {
return;
}
$message .= sprintf(
' (%s:%d called by %s:%d, %s, package %s)',
self::basename($backtrace[0]['file']),
$backtrace[0]['line'],
self::basename($backtrace[1]['file']),
$backtrace[1]['line'],
$link,
$package
);
@trigger_error($message, E_USER_DEPRECATED);
}
/**
* A non-local-aware version of PHPs basename function.
*/
private static function basename(string $filename): string
{
$pos = strrpos($filename, DIRECTORY_SEPARATOR);
if ($pos === false) {
return $filename;
}
return substr($filename, $pos + 1);
}
public static function enableTrackingDeprecations(): void
{
self::$type |= self::TYPE_TRACK_DEPRECATIONS;
}
public static function enableWithTriggerError(): void
{
self::$type |= self::TYPE_TRIGGER_ERROR;
}
public static function enableWithPsrLogger(LoggerInterface $logger): void
{
self::$type |= self::TYPE_PSR_LOGGER;
self::$logger = $logger;
}
public static function withoutDeduplication(): void
{
self::$deduplication = false;
}
public static function disable(): void
{
self::$type = self::TYPE_NONE;
self::$logger = null;
self::$deduplication = true;
foreach (self::$ignoredLinks as $link => $count) {
self::$ignoredLinks[$link] = 0;
}
}
public static function ignorePackage(string $packageName): void
{
self::$ignoredPackages[$packageName] = true;
}
public static function ignoreDeprecations(string ...$links): void
{
foreach ($links as $link) {
self::$ignoredLinks[$link] = 0;
}
}
public static function getUniqueTriggeredDeprecationsCount(): int
{
return array_reduce(self::$ignoredLinks, static function (int $carry, int $count) {
return $carry + $count;
}, 0);
}
/**
* Returns each triggered deprecation link identifier and the amount of occurrences.
*
* @return array<string,int>
*/
public static function getTriggeredDeprecations(): array
{
return self::$ignoredLinks;
}
}

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Doctrine\Deprecations\PHPUnit;
use Doctrine\Deprecations\Deprecation;
use function sprintf;
trait VerifyDeprecations
{
/** @var array<string,int> */
private $doctrineDeprecationsExpectations = [];
/** @var array<string,int> */
private $doctrineNoDeprecationsExpectations = [];
public function expectDeprecationWithIdentifier(string $identifier): void
{
$this->doctrineDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0;
}
public function expectNoDeprecationWithIdentifier(string $identifier): void
{
$this->doctrineNoDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0;
}
/**
* @before
*/
public function enableDeprecationTracking(): void
{
Deprecation::enableTrackingDeprecations();
}
/**
* @after
*/
public function verifyDeprecationsAreTriggered(): void
{
foreach ($this->doctrineDeprecationsExpectations as $identifier => $expectation) {
$actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0;
$this->assertTrue(
$actualCount > $expectation,
sprintf(
"Expected deprecation with identifier '%s' was not triggered by code executed in test.",
$identifier
)
);
}
foreach ($this->doctrineNoDeprecationsExpectations as $identifier => $expectation) {
$actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0;
$this->assertTrue(
$actualCount === $expectation,
sprintf(
"Expected deprecation with identifier '%s' was triggered by code executed in test, but expected not to.",
$identifier
)
);
}
}
}

View File

@ -0,0 +1,19 @@
Copyright (c) 2011 Michael Dowling <mtdowling@gmail.com>, 2016 Chris Tankersley <chris@ctankersley.com>, and contributors
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.

View File

@ -0,0 +1,47 @@
{
"name": "dragonmantank/cron-expression",
"type": "library",
"description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
"keywords": ["cron", "schedule"],
"license": "MIT",
"authors": [
{
"name": "Chris Tankersley",
"email": "chris@ctankersley.com",
"homepage": "https://github.com/dragonmantank"
}
],
"require": {
"php": "^7.2|^8.0",
"webmozart/assert": "^1.0"
},
"require-dev": {
"phpstan/phpstan": "^1.0",
"phpunit/phpunit": "^7.0|^8.0|^9.0",
"phpstan/phpstan-webmozart-assert": "^1.0",
"phpstan/extension-installer": "^1.0"
},
"autoload": {
"psr-4": {
"Cron\\": "src/Cron/"
}
},
"autoload-dev": {
"psr-4": {
"Cron\\Tests\\": "tests/Cron/"
}
},
"replace": {
"mtdowling/cron-expression": "^1.0"
},
"scripts": {
"phpstan": "./vendor/bin/phpstan analyze",
"test": "phpunit"
},
"config": {
"allow-plugins": {
"ocramius/package-versions": true,
"phpstan/extension-installer": true
}
}
}

View File

@ -0,0 +1,346 @@
<?php
declare(strict_types=1);
namespace Cron;
use DateTimeInterface;
/**
* Abstract CRON expression field.
*/
abstract class AbstractField implements FieldInterface
{
/**
* Full range of values that are allowed for this field type.
*
* @var array
*/
protected $fullRange = [];
/**
* Literal values we need to convert to integers.
*
* @var array
*/
protected $literals = [];
/**
* Start value of the full range.
*
* @var int
*/
protected $rangeStart;
/**
* End value of the full range.
*
* @var int
*/
protected $rangeEnd;
/**
* Constructor
*/
public function __construct()
{
$this->fullRange = range($this->rangeStart, $this->rangeEnd);
}
/**
* Check to see if a field is satisfied by a value.
*
* @internal
* @param int $dateValue Date value to check
* @param string $value Value to test
*
* @return bool
*/
public function isSatisfied(int $dateValue, string $value): bool
{
if ($this->isIncrementsOfRanges($value)) {
return $this->isInIncrementsOfRanges($dateValue, $value);
}
if ($this->isRange($value)) {
return $this->isInRange($dateValue, $value);
}
return '*' === $value || $dateValue === (int) $value;
}
/**
* Check if a value is a range.
*
* @internal
* @param string $value Value to test
*
* @return bool
*/
public function isRange(string $value): bool
{
return false !== strpos($value, '-');
}
/**
* Check if a value is an increments of ranges.
*
* @internal
* @param string $value Value to test
*
* @return bool
*/
public function isIncrementsOfRanges(string $value): bool
{
return false !== strpos($value, '/');
}
/**
* Test if a value is within a range.
*
* @internal
* @param int $dateValue Set date value
* @param string $value Value to test
*
* @return bool
*/
public function isInRange(int $dateValue, $value): bool
{
$parts = array_map(
function ($value) {
$value = trim($value);
return $this->convertLiterals($value);
},
explode('-', $value, 2)
);
return $dateValue >= $parts[0] && $dateValue <= $parts[1];
}
/**
* Test if a value is within an increments of ranges (offset[-to]/step size).
*
* @internal
* @param int $dateValue Set date value
* @param string $value Value to test
*
* @return bool
*/
public function isInIncrementsOfRanges(int $dateValue, string $value): bool
{
$chunks = array_map('trim', explode('/', $value, 2));
$range = $chunks[0];
$step = $chunks[1] ?? 0;
// No step or 0 steps aren't cool
/** @phpstan-ignore-next-line */
if (null === $step || '0' === $step || 0 === $step) {
return false;
}
// Expand the * to a full range
if ('*' === $range) {
$range = $this->rangeStart . '-' . $this->rangeEnd;
}
// Generate the requested small range
$rangeChunks = explode('-', $range, 2);
$rangeStart = (int) $rangeChunks[0];
$rangeEnd = $rangeChunks[1] ?? $rangeStart;
$rangeEnd = (int) $rangeEnd;
if ($rangeStart < $this->rangeStart || $rangeStart > $this->rangeEnd || $rangeStart > $rangeEnd) {
throw new \OutOfRangeException('Invalid range start requested');
}
if ($rangeEnd < $this->rangeStart || $rangeEnd > $this->rangeEnd || $rangeEnd < $rangeStart) {
throw new \OutOfRangeException('Invalid range end requested');
}
// Steps larger than the range need to wrap around and be handled
// slightly differently than smaller steps
// UPDATE - This is actually false. The C implementation will allow a
// larger step as valid syntax, it never wraps around. It will stop
// once it hits the end. Unfortunately this means in future versions
// we will not wrap around. However, because the logic exists today
// per the above documentation, fixing the bug from #89
if ($step > $this->rangeEnd) {
$thisRange = [$this->fullRange[$step % \count($this->fullRange)]];
} else {
if ($step > ($rangeEnd - $rangeStart)) {
$thisRange[$rangeStart] = (int) $rangeStart;
} else {
$thisRange = range($rangeStart, $rangeEnd, (int) $step);
}
}
return \in_array($dateValue, $thisRange, true);
}
/**
* Returns a range of values for the given cron expression.
*
* @param string $expression The expression to evaluate
* @param int $max Maximum offset for range
*
* @return array
*/
public function getRangeForExpression(string $expression, int $max): array
{
$values = [];
$expression = $this->convertLiterals($expression);
if (false !== strpos($expression, ',')) {
$ranges = explode(',', $expression);
$values = [];
foreach ($ranges as $range) {
$expanded = $this->getRangeForExpression($range, $this->rangeEnd);
$values = array_merge($values, $expanded);
}
return $values;
}
if ($this->isRange($expression) || $this->isIncrementsOfRanges($expression)) {
if (!$this->isIncrementsOfRanges($expression)) {
[$offset, $to] = explode('-', $expression);
$offset = $this->convertLiterals($offset);
$to = $this->convertLiterals($to);
$stepSize = 1;
} else {
$range = array_map('trim', explode('/', $expression, 2));
$stepSize = $range[1] ?? 0;
$range = $range[0];
$range = explode('-', $range, 2);
$offset = $range[0];
$to = $range[1] ?? $max;
}
$offset = '*' === $offset ? $this->rangeStart : $offset;
if ($stepSize >= $this->rangeEnd) {
$values = [$this->fullRange[$stepSize % \count($this->fullRange)]];
} else {
for ($i = $offset; $i <= $to; $i += $stepSize) {
$values[] = (int) $i;
}
}
sort($values);
} else {
$values = [$expression];
}
return $values;
}
/**
* Convert literal.
*
* @param string $value
*
* @return string
*/
protected function convertLiterals(string $value): string
{
if (\count($this->literals)) {
$key = array_search(strtoupper($value), $this->literals, true);
if (false !== $key) {
return (string) $key;
}
}
return $value;
}
/**
* Checks to see if a value is valid for the field.
*
* @param string $value
*
* @return bool
*/
public function validate(string $value): bool
{
$value = $this->convertLiterals($value);
// All fields allow * as a valid value
if ('*' === $value) {
return true;
}
// Validate each chunk of a list individually
if (false !== strpos($value, ',')) {
foreach (explode(',', $value) as $listItem) {
if (!$this->validate($listItem)) {
return false;
}
}
return true;
}
if (false !== strpos($value, '/')) {
[$range, $step] = explode('/', $value);
// Don't allow numeric ranges
if (is_numeric($range)) {
return false;
}
return $this->validate($range) && filter_var($step, FILTER_VALIDATE_INT);
}
if (false !== strpos($value, '-')) {
if (substr_count($value, '-') > 1) {
return false;
}
$chunks = explode('-', $value);
$chunks[0] = $this->convertLiterals($chunks[0]);
$chunks[1] = $this->convertLiterals($chunks[1]);
if ('*' === $chunks[0] || '*' === $chunks[1]) {
return false;
}
return $this->validate($chunks[0]) && $this->validate($chunks[1]);
}
if (!is_numeric($value)) {
return false;
}
if (false !== strpos($value, '.')) {
return false;
}
// We should have a numeric by now, so coerce this into an integer
$value = (int) $value;
return \in_array($value, $this->fullRange, true);
}
protected function timezoneSafeModify(DateTimeInterface $dt, string $modification): DateTimeInterface
{
$timezone = $dt->getTimezone();
$dt = $dt->setTimezone(new \DateTimeZone("UTC"));
$dt = $dt->modify($modification);
$dt = $dt->setTimezone($timezone);
return $dt;
}
protected function setTimeHour(DateTimeInterface $date, bool $invert, int $originalTimestamp): DateTimeInterface
{
$date = $date->setTime((int)$date->format('H'), ($invert ? 59 : 0));
// setTime caused the offset to change, moving time in the wrong direction
$actualTimestamp = $date->format('U');
if ((! $invert) && ($actualTimestamp <= $originalTimestamp)) {
$date = $this->timezoneSafeModify($date, "+1 hour");
} elseif ($invert && ($actualTimestamp >= $originalTimestamp)) {
$date = $this->timezoneSafeModify($date, "-1 hour");
}
return $date;
}
}

View File

@ -0,0 +1,568 @@
<?php
declare(strict_types=1);
namespace Cron;
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use Exception;
use InvalidArgumentException;
use LogicException;
use RuntimeException;
use Webmozart\Assert\Assert;
/**
* CRON expression parser that can determine whether or not a CRON expression is
* due to run, the next run date and previous run date of a CRON expression.
* The determinations made by this class are accurate if checked run once per
* minute (seconds are dropped from date time comparisons).
*
* Schedule parts must map to:
* minute [0-59], hour [0-23], day of month, month [1-12|JAN-DEC], day of week
* [1-7|MON-SUN], and an optional year.
*
* @see http://en.wikipedia.org/wiki/Cron
*/
class CronExpression
{
public const MINUTE = 0;
public const HOUR = 1;
public const DAY = 2;
public const MONTH = 3;
public const WEEKDAY = 4;
/** @deprecated */
public const YEAR = 5;
public const MAPPINGS = [
'@yearly' => '0 0 1 1 *',
'@annually' => '0 0 1 1 *',
'@monthly' => '0 0 1 * *',
'@weekly' => '0 0 * * 0',
'@daily' => '0 0 * * *',
'@midnight' => '0 0 * * *',
'@hourly' => '0 * * * *',
];
/**
* @var array CRON expression parts
*/
protected $cronParts;
/**
* @var FieldFactoryInterface CRON field factory
*/
protected $fieldFactory;
/**
* @var int Max iteration count when searching for next run date
*/
protected $maxIterationCount = 1000;
/**
* @var array Order in which to test of cron parts
*/
protected static $order = [
self::YEAR,
self::MONTH,
self::DAY,
self::WEEKDAY,
self::HOUR,
self::MINUTE,
];
/**
* @var array<string, string>
*/
private static $registeredAliases = self::MAPPINGS;
/**
* Registered a user defined CRON Expression Alias.
*
* @throws LogicException If the expression or the alias name are invalid
* or if the alias is already registered.
*/
public static function registerAlias(string $alias, string $expression): void
{
try {
new self($expression);
} catch (InvalidArgumentException $exception) {
throw new LogicException("The expression `$expression` is invalid", 0, $exception);
}
$shortcut = strtolower($alias);
if (1 !== preg_match('/^@\w+$/', $shortcut)) {
throw new LogicException("The alias `$alias` is invalid. It must start with an `@` character and contain alphanumeric (letters, numbers, regardless of case) plus underscore (_).");
}
if (isset(self::$registeredAliases[$shortcut])) {
throw new LogicException("The alias `$alias` is already registered.");
}
self::$registeredAliases[$shortcut] = $expression;
}
/**
* Unregistered a user defined CRON Expression Alias.
*
* @throws LogicException If the user tries to unregister a built-in alias
*/
public static function unregisterAlias(string $alias): bool
{
$shortcut = strtolower($alias);
if (isset(self::MAPPINGS[$shortcut])) {
throw new LogicException("The alias `$alias` is a built-in alias; it can not be unregistered.");
}
if (!isset(self::$registeredAliases[$shortcut])) {
return false;
}
unset(self::$registeredAliases[$shortcut]);
return true;
}
/**
* Tells whether a CRON Expression alias is registered.
*/
public static function supportsAlias(string $alias): bool
{
return isset(self::$registeredAliases[strtolower($alias)]);
}
/**
* Returns all registered aliases as an associated array where the aliases are the key
* and their associated expressions are the values.
*
* @return array<string, string>
*/
public static function getAliases(): array
{
return self::$registeredAliases;
}
/**
* @deprecated since version 3.0.2, use __construct instead.
*/
public static function factory(string $expression, FieldFactoryInterface $fieldFactory = null): CronExpression
{
/** @phpstan-ignore-next-line */
return new static($expression, $fieldFactory);
}
/**
* Validate a CronExpression.
*
* @param string $expression the CRON expression to validate
*
* @return bool True if a valid CRON expression was passed. False if not.
*/
public static function isValidExpression(string $expression): bool
{
try {
new CronExpression($expression);
} catch (InvalidArgumentException $e) {
return false;
}
return true;
}
/**
* Parse a CRON expression.
*
* @param string $expression CRON expression (e.g. '8 * * * *')
* @param null|FieldFactoryInterface $fieldFactory Factory to create cron fields
*/
public function __construct(string $expression, FieldFactoryInterface $fieldFactory = null)
{
$shortcut = strtolower($expression);
$expression = self::$registeredAliases[$shortcut] ?? $expression;
$this->fieldFactory = $fieldFactory ?: new FieldFactory();
$this->setExpression($expression);
}
/**
* Set or change the CRON expression.
*
* @param string $value CRON expression (e.g. 8 * * * *)
*
* @throws \InvalidArgumentException if not a valid CRON expression
*
* @return CronExpression
*/
public function setExpression(string $value): CronExpression
{
$split = preg_split('/\s/', $value, -1, PREG_SPLIT_NO_EMPTY);
Assert::isArray($split);
$this->cronParts = $split;
if (\count($this->cronParts) < 5) {
throw new InvalidArgumentException(
$value . ' is not a valid CRON expression'
);
}
foreach ($this->cronParts as $position => $part) {
$this->setPart($position, $part);
}
return $this;
}
/**
* Set part of the CRON expression.
*
* @param int $position The position of the CRON expression to set
* @param string $value The value to set
*
* @throws \InvalidArgumentException if the value is not valid for the part
*
* @return CronExpression
*/
public function setPart(int $position, string $value): CronExpression
{
if (!$this->fieldFactory->getField($position)->validate($value)) {
throw new InvalidArgumentException(
'Invalid CRON field value ' . $value . ' at position ' . $position
);
}
$this->cronParts[$position] = $value;
return $this;
}
/**
* Set max iteration count for searching next run dates.
*
* @param int $maxIterationCount Max iteration count when searching for next run date
*
* @return CronExpression
*/
public function setMaxIterationCount(int $maxIterationCount): CronExpression
{
$this->maxIterationCount = $maxIterationCount;
return $this;
}
/**
* Get a next run date relative to the current date or a specific date
*
* @param string|\DateTimeInterface $currentTime Relative calculation date
* @param int $nth Number of matches to skip before returning a
* matching next run date. 0, the default, will return the
* current date and time if the next run date falls on the
* current date and time. Setting this value to 1 will
* skip the first match and go to the second match.
* Setting this value to 2 will skip the first 2
* matches and so on.
* @param bool $allowCurrentDate Set to TRUE to return the current date if
* it matches the cron expression.
* @param null|string $timeZone TimeZone to use instead of the system default
*
* @throws \RuntimeException on too many iterations
* @throws \Exception
*
* @return \DateTime
*/
public function getNextRunDate($currentTime = 'now', int $nth = 0, bool $allowCurrentDate = false, $timeZone = null): DateTime
{
return $this->getRunDate($currentTime, $nth, false, $allowCurrentDate, $timeZone);
}
/**
* Get a previous run date relative to the current date or a specific date.
*
* @param string|\DateTimeInterface $currentTime Relative calculation date
* @param int $nth Number of matches to skip before returning
* @param bool $allowCurrentDate Set to TRUE to return the
* current date if it matches the cron expression
* @param null|string $timeZone TimeZone to use instead of the system default
*
* @throws \RuntimeException on too many iterations
* @throws \Exception
*
* @return \DateTime
*
* @see \Cron\CronExpression::getNextRunDate
*/
public function getPreviousRunDate($currentTime = 'now', int $nth = 0, bool $allowCurrentDate = false, $timeZone = null): DateTime
{
return $this->getRunDate($currentTime, $nth, true, $allowCurrentDate, $timeZone);
}
/**
* Get multiple run dates starting at the current date or a specific date.
*
* @param int $total Set the total number of dates to calculate
* @param string|\DateTimeInterface|null $currentTime Relative calculation date
* @param bool $invert Set to TRUE to retrieve previous dates
* @param bool $allowCurrentDate Set to TRUE to return the
* current date if it matches the cron expression
* @param null|string $timeZone TimeZone to use instead of the system default
*
* @return \DateTime[] Returns an array of run dates
*/
public function getMultipleRunDates(int $total, $currentTime = 'now', bool $invert = false, bool $allowCurrentDate = false, $timeZone = null): array
{
$timeZone = $this->determineTimeZone($currentTime, $timeZone);
if ('now' === $currentTime) {
$currentTime = new DateTime();
} elseif ($currentTime instanceof DateTime) {
$currentTime = clone $currentTime;
} elseif ($currentTime instanceof DateTimeImmutable) {
$currentTime = DateTime::createFromFormat('U', $currentTime->format('U'));
} elseif (\is_string($currentTime)) {
$currentTime = new DateTime($currentTime);
}
Assert::isInstanceOf($currentTime, DateTime::class);
$currentTime->setTimezone(new DateTimeZone($timeZone));
$matches = [];
for ($i = 0; $i < $total; ++$i) {
try {
$result = $this->getRunDate($currentTime, 0, $invert, $allowCurrentDate, $timeZone);
} catch (RuntimeException $e) {
break;
}
$allowCurrentDate = false;
$currentTime = clone $result;
$matches[] = $result;
}
return $matches;
}
/**
* Get all or part of the CRON expression.
*
* @param int|string|null $part specify the part to retrieve or NULL to get the full
* cron schedule string
*
* @return null|string Returns the CRON expression, a part of the
* CRON expression, or NULL if the part was specified but not found
*/
public function getExpression($part = null): ?string
{
if (null === $part) {
return implode(' ', $this->cronParts);
}
if (array_key_exists($part, $this->cronParts)) {
return $this->cronParts[$part];
}
return null;
}
/**
* Gets the parts of the cron expression as an array.
*
* @return string[]
* The array of parts that make up this expression.
*/
public function getParts()
{
return $this->cronParts;
}
/**
* Helper method to output the full expression.
*
* @return string Full CRON expression
*/
public function __toString(): string
{
return (string) $this->getExpression();
}
/**
* Determine if the cron is due to run based on the current date or a
* specific date. This method assumes that the current number of
* seconds are irrelevant, and should be called once per minute.
*
* @param string|\DateTimeInterface $currentTime Relative calculation date
* @param null|string $timeZone TimeZone to use instead of the system default
*
* @return bool Returns TRUE if the cron is due to run or FALSE if not
*/
public function isDue($currentTime = 'now', $timeZone = null): bool
{
$timeZone = $this->determineTimeZone($currentTime, $timeZone);
if ('now' === $currentTime) {
$currentTime = new DateTime();
} elseif ($currentTime instanceof DateTime) {
$currentTime = clone $currentTime;
} elseif ($currentTime instanceof DateTimeImmutable) {
$currentTime = DateTime::createFromFormat('U', $currentTime->format('U'));
} elseif (\is_string($currentTime)) {
$currentTime = new DateTime($currentTime);
}
Assert::isInstanceOf($currentTime, DateTime::class);
$currentTime->setTimezone(new DateTimeZone($timeZone));
// drop the seconds to 0
$currentTime->setTime((int) $currentTime->format('H'), (int) $currentTime->format('i'), 0);
try {
return $this->getNextRunDate($currentTime, 0, true)->getTimestamp() === $currentTime->getTimestamp();
} catch (Exception $e) {
return false;
}
}
/**
* Get the next or previous run date of the expression relative to a date.
*
* @param string|\DateTimeInterface|null $currentTime Relative calculation date
* @param int $nth Number of matches to skip before returning
* @param bool $invert Set to TRUE to go backwards in time
* @param bool $allowCurrentDate Set to TRUE to return the
* current date if it matches the cron expression
* @param string|null $timeZone TimeZone to use instead of the system default
*
* @throws \RuntimeException on too many iterations
* @throws Exception
*
* @return \DateTime
*/
protected function getRunDate($currentTime = null, int $nth = 0, bool $invert = false, bool $allowCurrentDate = false, $timeZone = null): DateTime
{
$timeZone = $this->determineTimeZone($currentTime, $timeZone);
if ($currentTime instanceof DateTime) {
$currentDate = clone $currentTime;
} elseif ($currentTime instanceof DateTimeImmutable) {
$currentDate = DateTime::createFromFormat('U', $currentTime->format('U'));
} elseif (\is_string($currentTime)) {
$currentDate = new DateTime($currentTime);
} else {
$currentDate = new DateTime('now');
}
Assert::isInstanceOf($currentDate, DateTime::class);
$currentDate->setTimezone(new DateTimeZone($timeZone));
// Workaround for setTime causing an offset change: https://bugs.php.net/bug.php?id=81074
$currentDate = DateTime::createFromFormat("!Y-m-d H:iO", $currentDate->format("Y-m-d H:iP"), $currentDate->getTimezone());
if ($currentDate === false) {
throw new \RuntimeException('Unable to create date from format');
}
$currentDate->setTimezone(new DateTimeZone($timeZone));
$nextRun = clone $currentDate;
// We don't have to satisfy * or null fields
$parts = [];
$fields = [];
foreach (self::$order as $position) {
$part = $this->getExpression($position);
if (null === $part || '*' === $part) {
continue;
}
$parts[$position] = $part;
$fields[$position] = $this->fieldFactory->getField($position);
}
if (isset($parts[self::DAY]) && isset($parts[self::WEEKDAY])) {
$domExpression = sprintf('%s %s %s %s *', $this->getExpression(0), $this->getExpression(1), $this->getExpression(2), $this->getExpression(3));
$dowExpression = sprintf('%s %s * %s %s', $this->getExpression(0), $this->getExpression(1), $this->getExpression(3), $this->getExpression(4));
$domExpression = new self($domExpression);
$dowExpression = new self($dowExpression);
$domRunDates = $domExpression->getMultipleRunDates($nth + 1, $currentTime, $invert, $allowCurrentDate, $timeZone);
$dowRunDates = $dowExpression->getMultipleRunDates($nth + 1, $currentTime, $invert, $allowCurrentDate, $timeZone);
if ($parts[self::DAY] === '?' || $parts[self::DAY] === '*') {
$domRunDates = [];
}
if ($parts[self::WEEKDAY] === '?' || $parts[self::WEEKDAY] === '*') {
$dowRunDates = [];
}
$combined = array_merge($domRunDates, $dowRunDates);
usort($combined, function ($a, $b) {
return $a->format('Y-m-d H:i:s') <=> $b->format('Y-m-d H:i:s');
});
if ($invert) {
$combined = array_reverse($combined);
}
return $combined[$nth];
}
// Set a hard limit to bail on an impossible date
for ($i = 0; $i < $this->maxIterationCount; ++$i) {
foreach ($parts as $position => $part) {
$satisfied = false;
// Get the field object used to validate this part
$field = $fields[$position];
// Check if this is singular or a list
if (false === strpos($part, ',')) {
$satisfied = $field->isSatisfiedBy($nextRun, $part, $invert);
} else {
foreach (array_map('trim', explode(',', $part)) as $listPart) {
if ($field->isSatisfiedBy($nextRun, $listPart, $invert)) {
$satisfied = true;
break;
}
}
}
// If the field is not satisfied, then start over
if (!$satisfied) {
$field->increment($nextRun, $invert, $part);
continue 2;
}
}
// Skip this match if needed
if ((!$allowCurrentDate && $nextRun == $currentDate) || --$nth > -1) {
$this->fieldFactory->getField(self::MINUTE)->increment($nextRun, $invert, $parts[self::MINUTE] ?? null);
continue;
}
return $nextRun;
}
// @codeCoverageIgnoreStart
throw new RuntimeException('Impossible CRON expression');
// @codeCoverageIgnoreEnd
}
/**
* Workout what timeZone should be used.
*
* @param string|\DateTimeInterface|null $currentTime Relative calculation date
* @param string|null $timeZone TimeZone to use instead of the system default
*
* @return string
*/
protected function determineTimeZone($currentTime, ?string $timeZone): string
{
if (null !== $timeZone) {
return $timeZone;
}
if ($currentTime instanceof DateTimeInterface) {
return $currentTime->getTimezone()->getName();
}
return date_default_timezone_get();
}
}

View File

@ -0,0 +1,164 @@
<?php
declare(strict_types=1);
namespace Cron;
use DateTime;
use DateTimeInterface;
/**
* Day of month field. Allows: * , / - ? L W.
*
* 'L' stands for "last" and specifies the last day of the month.
*
* The 'W' character is used to specify the weekday (Monday-Friday) nearest the
* given day. As an example, if you were to specify "15W" as the value for the
* day-of-month field, the meaning is: "the nearest weekday to the 15th of the
* month". So if the 15th is a Saturday, the trigger will fire on Friday the
* 14th. If the 15th is a Sunday, the trigger will fire on Monday the 16th. If
* the 15th is a Tuesday, then it will fire on Tuesday the 15th. However if you
* specify "1W" as the value for day-of-month, and the 1st is a Saturday, the
* trigger will fire on Monday the 3rd, as it will not 'jump' over the boundary
* of a month's days. The 'W' character can only be specified when the
* day-of-month is a single day, not a range or list of days.
*
* @author Michael Dowling <mtdowling@gmail.com>
*/
class DayOfMonthField extends AbstractField
{
/**
* {@inheritdoc}
*/
protected $rangeStart = 1;
/**
* {@inheritdoc}
*/
protected $rangeEnd = 31;
/**
* Get the nearest day of the week for a given day in a month.
*
* @param int $currentYear Current year
* @param int $currentMonth Current month
* @param int $targetDay Target day of the month
*
* @return \DateTime|null Returns the nearest date
*/
private static function getNearestWeekday(int $currentYear, int $currentMonth, int $targetDay): ?DateTime
{
$tday = str_pad((string) $targetDay, 2, '0', STR_PAD_LEFT);
$target = DateTime::createFromFormat('Y-m-d', "{$currentYear}-{$currentMonth}-{$tday}");
if ($target === false) {
return null;
}
$currentWeekday = (int) $target->format('N');
if ($currentWeekday < 6) {
return $target;
}
$lastDayOfMonth = $target->format('t');
foreach ([-1, 1, -2, 2] as $i) {
$adjusted = $targetDay + $i;
if ($adjusted > 0 && $adjusted <= $lastDayOfMonth) {
$target->setDate($currentYear, $currentMonth, $adjusted);
if ((int) $target->format('N') < 6 && (int) $target->format('m') === $currentMonth) {
return $target;
}
}
}
return null;
}
/**
* {@inheritdoc}
*/
public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert): bool
{
// ? states that the field value is to be skipped
if ('?' === $value) {
return true;
}
$fieldValue = $date->format('d');
// Check to see if this is the last day of the month
if ('L' === $value) {
return $fieldValue === $date->format('t');
}
// Check to see if this is the nearest weekday to a particular value
if ($wPosition = strpos($value, 'W')) {
// Parse the target day
$targetDay = (int) substr($value, 0, $wPosition);
// Find out if the current day is the nearest day of the week
$nearest = self::getNearestWeekday(
(int) $date->format('Y'),
(int) $date->format('m'),
$targetDay
);
if ($nearest) {
return $date->format('j') === $nearest->format('j');
}
throw new \RuntimeException('Unable to return nearest weekday');
}
return $this->isSatisfied((int) $date->format('d'), $value);
}
/**
* @inheritDoc
*
* @param \DateTime|\DateTimeImmutable $date
*/
public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface
{
if (! $invert) {
$date = $date->add(new \DateInterval('P1D'));
$date = $date->setTime(0, 0);
} else {
$date = $date->sub(new \DateInterval('P1D'));
$date = $date->setTime(23, 59);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function validate(string $value): bool
{
$basicChecks = parent::validate($value);
// Validate that a list don't have W or L
if (false !== strpos($value, ',') && (false !== strpos($value, 'W') || false !== strpos($value, 'L'))) {
return false;
}
if (!$basicChecks) {
if ('?' === $value) {
return true;
}
if ('L' === $value) {
return true;
}
if (preg_match('/^(.*)W$/', $value, $matches)) {
return $this->validate($matches[1]);
}
return false;
}
return $basicChecks;
}
}

View File

@ -0,0 +1,194 @@
<?php
declare(strict_types=1);
namespace Cron;
use DateTimeInterface;
use InvalidArgumentException;
/**
* Day of week field. Allows: * / , - ? L #.
*
* Days of the week can be represented as a number 0-7 (0|7 = Sunday)
* or as a three letter string: SUN, MON, TUE, WED, THU, FRI, SAT.
*
* 'L' stands for "last". It allows you to specify constructs such as
* "the last Friday" of a given month.
*
* '#' is allowed for the day-of-week field, and must be followed by a
* number between one and five. It allows you to specify constructs such as
* "the second Friday" of a given month.
*/
class DayOfWeekField extends AbstractField
{
/**
* {@inheritdoc}
*/
protected $rangeStart = 0;
/**
* {@inheritdoc}
*/
protected $rangeEnd = 7;
/**
* @var array Weekday range
*/
protected $nthRange;
/**
* {@inheritdoc}
*/
protected $literals = [1 => 'MON', 2 => 'TUE', 3 => 'WED', 4 => 'THU', 5 => 'FRI', 6 => 'SAT', 7 => 'SUN'];
/**
* Constructor
*/
public function __construct()
{
$this->nthRange = range(1, 5);
parent::__construct();
}
/**
* @inheritDoc
*/
public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert): bool
{
if ('?' === $value) {
return true;
}
// Convert text day of the week values to integers
$value = $this->convertLiterals($value);
$currentYear = (int) $date->format('Y');
$currentMonth = (int) $date->format('m');
$lastDayOfMonth = (int) $date->format('t');
// Find out if this is the last specific weekday of the month
if ($lPosition = strpos($value, 'L')) {
$weekday = $this->convertLiterals(substr($value, 0, $lPosition));
$weekday %= 7;
$daysInMonth = (int) $date->format('t');
$remainingDaysInMonth = $daysInMonth - (int) $date->format('d');
return (($weekday === (int) $date->format('w')) && ($remainingDaysInMonth < 7));
}
// Handle # hash tokens
if (strpos($value, '#')) {
[$weekday, $nth] = explode('#', $value);
if (!is_numeric($nth)) {
throw new InvalidArgumentException("Hashed weekdays must be numeric, {$nth} given");
} else {
$nth = (int) $nth;
}
// 0 and 7 are both Sunday, however 7 matches date('N') format ISO-8601
if ('0' === $weekday) {
$weekday = 7;
}
$weekday = (int) $this->convertLiterals((string) $weekday);
// Validate the hash fields
if ($weekday < 0 || $weekday > 7) {
throw new InvalidArgumentException("Weekday must be a value between 0 and 7. {$weekday} given");
}
if (!\in_array($nth, $this->nthRange, true)) {
throw new InvalidArgumentException("There are never more than 5 or less than 1 of a given weekday in a month, {$nth} given");
}
// The current weekday must match the targeted weekday to proceed
if ((int) $date->format('N') !== $weekday) {
return false;
}
$tdate = clone $date;
$tdate = $tdate->setDate($currentYear, $currentMonth, 1);
$dayCount = 0;
$currentDay = 1;
while ($currentDay < $lastDayOfMonth + 1) {
if ((int) $tdate->format('N') === $weekday) {
if (++$dayCount >= $nth) {
break;
}
}
$tdate = $tdate->setDate($currentYear, $currentMonth, ++$currentDay);
}
return (int) $date->format('j') === $currentDay;
}
// Handle day of the week values
if (false !== strpos($value, '-')) {
$parts = explode('-', $value);
if ('7' === $parts[0]) {
$parts[0] = 0;
} elseif ('0' === $parts[1]) {
$parts[1] = 7;
}
$value = implode('-', $parts);
}
// Test to see which Sunday to use -- 0 == 7 == Sunday
$format = \in_array(7, array_map(function ($value) {
return (int) $value;
}, str_split($value)), true) ? 'N' : 'w';
$fieldValue = (int) $date->format($format);
return $this->isSatisfied($fieldValue, $value);
}
/**
* @inheritDoc
*/
public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface
{
if (! $invert) {
$date = $date->add(new \DateInterval('P1D'));
$date = $date->setTime(0, 0);
} else {
$date = $date->sub(new \DateInterval('P1D'));
$date = $date->setTime(23, 59);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function validate(string $value): bool
{
$basicChecks = parent::validate($value);
if (!$basicChecks) {
if ('?' === $value) {
return true;
}
// Handle the # value
if (false !== strpos($value, '#')) {
$chunks = explode('#', $value);
$chunks[0] = $this->convertLiterals($chunks[0]);
if (parent::validate($chunks[0]) && is_numeric($chunks[1]) && \in_array((int) $chunks[1], $this->nthRange, true)) {
return true;
}
}
if (preg_match('/^(.*)L$/', $value, $matches)) {
return $this->validate($matches[1]);
}
return false;
}
return $basicChecks;
}
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Cron;
use InvalidArgumentException;
/**
* CRON field factory implementing a flyweight factory.
*
* @see http://en.wikipedia.org/wiki/Cron
*/
class FieldFactory implements FieldFactoryInterface
{
/**
* @var array Cache of instantiated fields
*/
private $fields = [];
/**
* Get an instance of a field object for a cron expression position.
*
* @param int $position CRON expression position value to retrieve
*
* @throws InvalidArgumentException if a position is not valid
*/
public function getField(int $position): FieldInterface
{
return $this->fields[$position] ?? $this->fields[$position] = $this->instantiateField($position);
}
private function instantiateField(int $position): FieldInterface
{
switch ($position) {
case CronExpression::MINUTE:
return new MinutesField();
case CronExpression::HOUR:
return new HoursField();
case CronExpression::DAY:
return new DayOfMonthField();
case CronExpression::MONTH:
return new MonthField();
case CronExpression::WEEKDAY:
return new DayOfWeekField();
}
throw new InvalidArgumentException(
($position + 1) . ' is not a valid position'
);
}
}

Some files were not shown because too many files have changed in this diff Show More