From 3e97c51418e6d27e9a81906f347fcb7c78e27d4f Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 14 Apr 2024 15:23:16 +0200 Subject: Adding upstream version 0.20.0. Signed-off-by: Daniel Baumann --- vendor/gipfl/web/LICENSE | 21 + vendor/gipfl/web/composer.json | 25 + vendor/gipfl/web/public/css/01-gipfl-base.less | 8 + .../gipfl/web/public/css/21-gipfl-collapsible.less | 55 ++ .../gipfl/web/public/css/21-gipfl-widget-hint.less | 57 ++ .../web/public/css/31-gipfl-name-value-table.less | 27 + vendor/gipfl/web/public/css/40-gipfl-form.less | 69 ++ vendor/gipfl/web/public/css/41-director-form.less | 339 ++++++++++ .../web/public/css/42-director-extensible-set.less | 33 + vendor/gipfl/web/public/css/43-inline-form.less | 29 + vendor/gipfl/web/public/css/81-phpdiff.less | 97 +++ vendor/gipfl/web/public/js/module.js | 69 ++ vendor/gipfl/web/src/Form.php | 281 ++++++++ .../gipfl/web/src/Form/Decorator/DdDtDecorator.php | 158 +++++ vendor/gipfl/web/src/Form/Element/Boolean.php | 39 ++ vendor/gipfl/web/src/Form/Element/MultiSelect.php | 119 ++++ vendor/gipfl/web/src/Form/Element/Password.php | 11 + .../web/src/Form/Element/TextWithActionButton.php | 104 +++ .../web/src/Form/Feature/NextConfirmCancel.php | 153 +++++ .../web/src/Form/Validator/AlwaysFailValidator.php | 16 + .../PhpSessionBasedCsrfTokenValidator.php | 34 + .../web/src/Form/Validator/SimpleValidator.php | 27 + vendor/gipfl/web/src/HtmlHelper.php | 29 + vendor/gipfl/web/src/InlineForm.php | 10 + vendor/gipfl/web/src/Table/NameValueTable.php | 47 ++ vendor/gipfl/web/src/Widget/CollapsibleList.php | 74 ++ vendor/gipfl/web/src/Widget/ConfigDiff.php | 106 +++ vendor/gipfl/web/src/Widget/Hint.php | 45 ++ vendor/gipfl/web/src/vendor/php-diff/lib/Diff.php | 179 +++++ .../vendor/php-diff/lib/Diff/Renderer/Abstract.php | 82 +++ .../php-diff/lib/Diff/Renderer/Html/Array.php | 230 +++++++ .../php-diff/lib/Diff/Renderer/Html/Inline.php | 143 ++++ .../php-diff/lib/Diff/Renderer/Html/SideBySide.php | 163 +++++ .../php-diff/lib/Diff/Renderer/Text/Context.php | 128 ++++ .../php-diff/lib/Diff/Renderer/Text/Unified.php | 87 +++ .../vendor/php-diff/lib/Diff/SequenceMatcher.php | 742 +++++++++++++++++++++ 36 files changed, 3836 insertions(+) create mode 100644 vendor/gipfl/web/LICENSE create mode 100644 vendor/gipfl/web/composer.json create mode 100644 vendor/gipfl/web/public/css/01-gipfl-base.less create mode 100644 vendor/gipfl/web/public/css/21-gipfl-collapsible.less create mode 100644 vendor/gipfl/web/public/css/21-gipfl-widget-hint.less create mode 100644 vendor/gipfl/web/public/css/31-gipfl-name-value-table.less create mode 100644 vendor/gipfl/web/public/css/40-gipfl-form.less create mode 100644 vendor/gipfl/web/public/css/41-director-form.less create mode 100644 vendor/gipfl/web/public/css/42-director-extensible-set.less create mode 100644 vendor/gipfl/web/public/css/43-inline-form.less create mode 100644 vendor/gipfl/web/public/css/81-phpdiff.less create mode 100644 vendor/gipfl/web/public/js/module.js create mode 100644 vendor/gipfl/web/src/Form.php create mode 100644 vendor/gipfl/web/src/Form/Decorator/DdDtDecorator.php create mode 100644 vendor/gipfl/web/src/Form/Element/Boolean.php create mode 100644 vendor/gipfl/web/src/Form/Element/MultiSelect.php create mode 100644 vendor/gipfl/web/src/Form/Element/Password.php create mode 100644 vendor/gipfl/web/src/Form/Element/TextWithActionButton.php create mode 100644 vendor/gipfl/web/src/Form/Feature/NextConfirmCancel.php create mode 100644 vendor/gipfl/web/src/Form/Validator/AlwaysFailValidator.php create mode 100644 vendor/gipfl/web/src/Form/Validator/PhpSessionBasedCsrfTokenValidator.php create mode 100644 vendor/gipfl/web/src/Form/Validator/SimpleValidator.php create mode 100644 vendor/gipfl/web/src/HtmlHelper.php create mode 100644 vendor/gipfl/web/src/InlineForm.php create mode 100644 vendor/gipfl/web/src/Table/NameValueTable.php create mode 100644 vendor/gipfl/web/src/Widget/CollapsibleList.php create mode 100644 vendor/gipfl/web/src/Widget/ConfigDiff.php create mode 100644 vendor/gipfl/web/src/Widget/Hint.php create mode 100644 vendor/gipfl/web/src/vendor/php-diff/lib/Diff.php create mode 100644 vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Abstract.php create mode 100644 vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Array.php create mode 100644 vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Inline.php create mode 100644 vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/SideBySide.php create mode 100644 vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Context.php create mode 100644 vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Unified.php create mode 100644 vendor/gipfl/web/src/vendor/php-diff/lib/Diff/SequenceMatcher.php (limited to 'vendor/gipfl/web') diff --git a/vendor/gipfl/web/LICENSE b/vendor/gipfl/web/LICENSE new file mode 100644 index 0000000..dd88e09 --- /dev/null +++ b/vendor/gipfl/web/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2018 Thomas Gelf + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/gipfl/web/composer.json b/vendor/gipfl/web/composer.json new file mode 100644 index 0000000..ce28dd5 --- /dev/null +++ b/vendor/gipfl/web/composer.json @@ -0,0 +1,25 @@ +{ + "name": "gipfl/web", + "description": "Various web widgets", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Thomas Gelf", + "email": "thomas@gelf.net" + } + ], + "config": { + "sort-packages": true + }, + "autoload": { + "psr-4": { + "gipfl\\Web\\": "src" + } + }, + "require": { + "php": ">=5.6.0", + "ipl/html": ">=0.3", + "gipfl/translation": ">=0.1.1" + } +} diff --git a/vendor/gipfl/web/public/css/01-gipfl-base.less b/vendor/gipfl/web/public/css/01-gipfl-base.less new file mode 100644 index 0000000..c352858 --- /dev/null +++ b/vendor/gipfl/web/public/css/01-gipfl-base.less @@ -0,0 +1,8 @@ +@gipfl-color-low-sat-blue: #dae3e6; +// map Icinga vars, to avoid warnings in other files +@gipfl-color-main: @icinga-blue; +@gipfl-color-text: @text-color; +@gipfl-color-gray-light: @gray-light; +@gipfl-bg-element: #f2f1f0; +@gipfl-bg-element: @low-sat-blue; +@gipfl-icon-font: 'ifont'; diff --git a/vendor/gipfl/web/public/css/21-gipfl-collapsible.less b/vendor/gipfl/web/public/css/21-gipfl-collapsible.less new file mode 100644 index 0000000..da065c1 --- /dev/null +++ b/vendor/gipfl/web/public/css/21-gipfl-collapsible.less @@ -0,0 +1,55 @@ +/** + * Lightweight support for small collapsible components + * + * @since v0.6.0 + */ +.gipfl-collapsible-control { + display: block; + border-bottom: 1px solid @gipfl-color-gray-light; + &:hover { + cursor: pointer; + text-decoration: none; + } + &::after { + font-family: @gipfl-icon-font; + content: '\f103'; + float: right; + margin-right: 0.5em; + color: @gipfl-color-gray-light; + } + &:hover { + &::after { + color: @gipfl-color-text; + } + } + &:focus { + text-decoration: none; + &::after { + color: @gipfl-color-main; + } + } +} + +.gipfl-collapsible .collapsed { + :not(.gipfl-collapsible-control) { + display: none; + } + .gipfl-collapsible-control { + &::after { + content: '\e87a'; + } + } +} + +ul.gipfl-collapsible { + list-style-type: none; + margin: 0; + padding: 0; + li { + margin: 0; + } + .gipfl-collapsible-control { + font-weight: bold; + line-height: 2em; + } +} diff --git a/vendor/gipfl/web/public/css/21-gipfl-widget-hint.less b/vendor/gipfl/web/public/css/21-gipfl-widget-hint.less new file mode 100644 index 0000000..2a4f9f9 --- /dev/null +++ b/vendor/gipfl/web/public/css/21-gipfl-widget-hint.less @@ -0,0 +1,57 @@ +/** + * State Hints + * + * @since v0.6.0 + */ +div.gipfl-widget-hint { + border: 1px solid @text-color; + padding: 0.5em; + line-height: 2em; + max-width: 60em; + border-left-width: 3em; + border-radius: 0.25em; + margin-bottom: 1em; + background-color: @body-bg-color; + box-shadow: fade(@text-color, 30%) 1em 0 1.5em 0; + &:before { + position: relative; + margin-left: -1.4em; + margin-right: 0.5em; + height: 100%; + vertical-align: middle; + font-family: 'ifont'; + color: white; + font-size: 2em; + } + &.ok { + border-color: @color-ok; + box-shadow: fade(@color-ok, 30%) 1em 0 1.5em 0; + } + &.info { + border-color: @color-pending; + box-shadow: fade(@color-pending, 30%) 1em 0 1.5em 0; + } + &.warning { + border-color: @color-warning; + box-shadow: fade(@color-warning, 30%) 1em 0 1.5em 0; + } + &.error { + border-color: @color-critical; + box-shadow: fade(@color-critical, 30%) 1em 0 1.5em 0; + } + &.critical:before, &.error:before { + content: '\e881'; + } + &.warning:before { + content: '\e881'; + } + &.info:before { + content: '\e87d'; + } + &.ok:before { + content: '\e803'; + } + a { + text-decoration: underline; + } +} diff --git a/vendor/gipfl/web/public/css/31-gipfl-name-value-table.less b/vendor/gipfl/web/public/css/31-gipfl-name-value-table.less new file mode 100644 index 0000000..30ae217 --- /dev/null +++ b/vendor/gipfl/web/public/css/31-gipfl-name-value-table.less @@ -0,0 +1,27 @@ +table.gipfl-name-value-table { + width: 100%; + > tbody > tr > th { + color: @text-color-light; + // Reset default font-weight + font-weight: bold; + padding-left: 0; + text-align: right; + vertical-align: top; + width: 12em; + } + > tbody > tr > td { + vertical-align: top; + } +} + +table.gipfl-name-value-table a { + color: @icinga-blue; +} + +table.gipfl-name-value-table pre { + background-color: transparent; + margin: 0; + padding: 0; + font-size: unset; + display: inline; +} diff --git a/vendor/gipfl/web/public/css/40-gipfl-form.less b/vendor/gipfl/web/public/css/40-gipfl-form.less new file mode 100644 index 0000000..cf6722e --- /dev/null +++ b/vendor/gipfl/web/public/css/40-gipfl-form.less @@ -0,0 +1,69 @@ +form.gipfl-form { + input[type="submit"] { + margin-right: 0.5em; + } + input[type="submit"]:first-of-type { + background-color: @icinga-blue; + color: @text-color-inverted; + border: 2px solid @icinga-blue; + font-weight: bold; + } + + select:not([multiple]) { + padding-right: 1.5625em; + background-image: url(../img/select-icon.svg); + background-repeat: no-repeat; + background-position: right center; + background-size: contain; + } + + input.validated { + background-color: fade(@color-ok, 30%); + border-color: @color-ok; + } + + :not(output):-moz-ui-invalid, :not(output).invalid { + background-color: fade(@color-critical, 30%); + border-color: @color-critical; + box-shadow: none; + } + + input[type=text].input-with-button { + max-width: 30em; + min-width: 18em; + width: 80%; + margin: 0; + border-top-right-radius: unset; + border-bottom-right-radius: unset; + border-right-style: none; + &:focus { + border-right-style: none; + } + } + + input[type=submit].input-element-related-button { + width: 20%; + max-width: 6em; + background-color: @icinga-blue; + color: @text-color-inverted; + border: 1px solid @icinga-blue; + border-radius: 0 3px 3px 0; + -moz-border-radius: 0 3px 3px 0; + -webkit-border-radius: 0 3px 3px 0; + line-height: 2em; + height: 2.4em; + padding: 0; + margin: 0 1em 0 0; + &:hover { + background-color: @text-color-inverted; + color: @icinga-blue; + border-color: @icinga-blue; + } + } + p.gipfl-widget-hint { + max-width: 52.5em; + } + p.gipfl-element-description { + max-width: 36em; + } +} diff --git a/vendor/gipfl/web/public/css/41-director-form.less b/vendor/gipfl/web/public/css/41-director-form.less new file mode 100644 index 0000000..b907593 --- /dev/null +++ b/vendor/gipfl/web/public/css/41-director-form.less @@ -0,0 +1,339 @@ +form.gipfl-form { + label { + line-height: 2em; + } + dl { + margin: 0; + padding: 0; + &.active { + dt label { + text-decoration: underline; + } + } + } + + dt { + padding: 0; + margin: 0.5em 0 0 0; + display: inline-block; + vertical-align: top; + min-width: 8em; + max-width: 16em; + min-height: 2.5em; + width: 27%; + label { + color: @text-color; + font-weight: bold; + width: 12em; + font-size: inherit; + + &:hover { + text-decoration: underline; + cursor: pointer; + } + } + + &.errors label { + color: @color-critical; + } + } + + dd { + padding: 0.3em 0.5em; + margin: 0; + display: inline-block; + width: 73%; + min-height: 2.5em; + vertical-align: top; + + &.errors { + input[type=text], select { + border-color: @color-critical; + } + } + + &.full-width { + padding: 0.5em; + width: 100%; + } + + &:after { + display: block; + content: ''; + } + + ul.errors, ul.gipfl-form-element-errors { + list-style-type: none; + padding-left: 0.3em; + + li { + color: @colorCritical; + padding: 0.3em; + } + } + } + + input[type="text"], + input[type="password"], + input[type="number"], + input[type="datetime-local"], + input[type="date"], + input[type="time"], + textarea, + select { + background-color: @gipfl-bg-element; + } + + .errors label { + color: @color-critical; + } + + textarea { + height: auto; + max-width: 100%; + } + + input[type=file] { + background-color: @text-color-inverted; + padding-right: 1em; + } + + input[type=submit] { + .button(); + transition: none; // Avoid flickering on autosubmit + border-width: 1px; + margin-top: 0.5em; + + &:disabled { + border-color: @gray-light; + // background-color: @gray-light; + // color: #fff; + cursor: wait; + } + &:first-of-type { + border-width: 2px; + } + } + + select { + border: 1px solid #ddd; + cursor: pointer; + } + + input, + select, + select option, + textarea { + -webkit-appearance: none; + -moz-appearance: none; + } + + select::-ms-expand, + input::-ms-expand, + textarea::-ms-expand { // for IE 11 + display: none; + } + + input[type=submit].link-button { + color: @icinga-blue; + background: none; + border: none; + font-weight: normal; + padding: 0; + margin: 0; + text-align: left; + + &:hover { + text-decoration: underline; + } + } + + ul.form-errors { + list-style-type: none; + margin-bottom: 0.5em; + padding: 0; + + ul.errors { + list-style-type: none; + padding: 0; + } + + ul.errors li { + background: @color-critical; + font-weight: bold; + padding: 0.5em 1em; + color: white; + } + } + + input[type=text], input[type=password], textarea, select { + max-width: 36em; + min-width: 20em; + width: 100%; + } + input:not([type=submit]), + textarea, + select { + line-height: 2em; + height: 2.4em; + padding-left: 0.5em; + border-style: solid; + border-color: @gray-lighter; + border-width: 1px; + border-radius: 3px; + color: @text-color; + + &:hover { + border-color: @gray; + } + + &:focus, &:focus:hover { + border-color: @icinga-blue; + outline: none; + } + } + // duplicated from 40 -> ordering problem + input.validated { + background-color: fade(@color-ok, 30%); + border-color: @color-ok; + } + textarea { + height: unset; + min-height: 2.4em; + resize: vertical; + } + + input.search { + background-image: url("../img/icons/search.png"); + background-repeat: no-repeat; + padding-left: 2em; + } + + select[multiple], select[multiple=multiple] { + height: auto; + } + + select option { + height: 2em; + padding-top: 0.3em; + } + + #_FAKE_SUBMIT { + position: absolute; + left: -100%; + } + + select::-moz-focus-inner { + border: 0; + } + + select:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 #000; + } + + select[value=""] { + color: blue; + border: 1px solid #666; + // background-color: white; + } + + select option { + color: inherit; + padding-left: 0.5em; + } + + // default option + select option[value=""] { + // color: #aaa; + } + + fieldset { + margin: 0; + min-width: 36em; + + padding: 0 0 1.5em 0; + border: none; + + legend { + margin: 0 0 0.5em 0; + font-size: 1em; + border-bottom: 1px solid @gray-light; + font-weight: bold; + display: block; + width: 100%; + padding-left: 1em; + line-height: 2em; + cursor: pointer; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + + &:hover { + border-color: @text-color; + } + + &::before { + // icon: down-dir + font-family: 'ifont'; + content: '\e81d'; + margin-left: -1em; + padding-top: 0; + float: left; + color: inherit; + } + } + + &.collapsed { + dl, dd, dt, ul, div { + display: none; + } + + legend { + margin: 0; + } + legend::before { + // icon: right-dir + content: '\e820'; + } + + margin-bottom: 0.2em; + padding-bottom: 0; + } + } +} + +#layout.minimal-layout div.content form.gipfl-form { + dt, dd { + display: block; + width: auto; + } + + dt label { + color: @text-color; + } + + fieldset { + min-width: unset; + } + + input[type=text], input[type=password], textarea, select { + max-width: unset; + min-width: unset; + border-color: @gray-light; + } + + dd.active { + input[type=text], input[type=password], textarea, select { + border-color: @icinga-blue; + } + } + + fieldset.collapsed { + dd, dt, ul, div { + display: none; + } + } +} + diff --git a/vendor/gipfl/web/public/css/42-director-extensible-set.less b/vendor/gipfl/web/public/css/42-director-extensible-set.less new file mode 100644 index 0000000..c5380cd --- /dev/null +++ b/vendor/gipfl/web/public/css/42-director-extensible-set.less @@ -0,0 +1,33 @@ +#layout.minimal-layout div.content form.gipfl-form { + ul.extensible-set { + max-width: unset; + border: 1px solid @gray-light; + } + + dd.active ul.extensible-set { + border: 1px solid @icinga-blue; + + input[type=submit]:first-of-type { + border-width: 1px; + } + } + + dd input.related-action[type='submit'] { + display: none; + } + + dd.active li.active input.related-action[type='submit'] { + display: inline-block; + } + + dd.active ul.extensible-set, ul.extensible-set.sortable { + input[type=text], select { + width: 100%; + } + + input[type=text] { + // background-color: white; + border: 1px solid white; + } + } +} diff --git a/vendor/gipfl/web/public/css/43-inline-form.less b/vendor/gipfl/web/public/css/43-inline-form.less new file mode 100644 index 0000000..10d214c --- /dev/null +++ b/vendor/gipfl/web/public/css/43-inline-form.less @@ -0,0 +1,29 @@ +form.gipfl-inline-form { + display: inline-block; + select { + width: auto; + min-width: unset; + max-width: unset; + border: none; + padding: 0 0.5em; + margin: 0; + line-height: unset; + box-sizing:border-box; + height: 1.5em; + + &:hover { + border: none; + } + + &:focus { + border: none; + } + } + input[type=submit] { + padding: 0 0.25em; + margin: 0 1em 0 0; + border: none; + line-height: unset; + box-sizing: border-box; + } +} diff --git a/vendor/gipfl/web/public/css/81-phpdiff.less b/vendor/gipfl/web/public/css/81-phpdiff.less new file mode 100644 index 0000000..bc634b1 --- /dev/null +++ b/vendor/gipfl/web/public/css/81-phpdiff.less @@ -0,0 +1,97 @@ +.Differences { + width: 100%; + table-layout: fixed; + empty-cells: show; +} + +.Differences thead { + display: none; +} + +.Differences thead th { + text-align: left; + padding-left: 4 / 14 * 16em; +} +.Differences tbody th { + text-align: right; + width: 4em; + padding: 1px 2px; + border-right: 1px solid @gray-light; + background: @gray-lightest; + font-weight: normal; + vertical-align: top; +} + +.Differences tbody td { + width: 50%; + .preformatted(); + word-break: break-all; +} + +@color-diff-ins: #bfb; +@color-diff-del: #faa; +@color-diff-changed-old: #fdd; +@color-diff-changed-new: #efe; + +.DifferencesSideBySide { + ins, del { + text-decoration: none; + } + + .ChangeInsert { + td.Left { + background: @gray-lighter; + } + td.Right { + background: @color-diff-ins; + } + } + + .ChangeDelete { + td.Left { + background: @color-diff-del; + } + td.Right { + background: @gray-lighter; + } + } + + .ChangeReplace { + td.Left { + background: @color-diff-changed-old; + del { + background: @color-diff-del; + } + } + + td.Right { + background: @color-diff-changed-new; + ins { + background: @color-diff-ins; + } + } + + } +} + +.Differences .Skipped { + background: @gray-lightest; +} + +.DifferencesInline .ChangeReplace .Left, +.DifferencesInline .ChangeDelete .Left { + background: #fdd; +} + +.DifferencesInline .ChangeReplace .Right, +.DifferencesInline .ChangeInsert .Right { + background: #dfd; +} + +.DifferencesInline .ChangeReplace ins { + background: #9e9; +} + +.DifferencesInline .ChangeReplace del { + background: #e99; +} diff --git a/vendor/gipfl/web/public/js/module.js b/vendor/gipfl/web/public/js/module.js new file mode 100644 index 0000000..83b19a5 --- /dev/null +++ b/vendor/gipfl/web/public/js/module.js @@ -0,0 +1,69 @@ +(function(window, $) { + 'use strict'; + + var Web = function () { + }; + + Web.prototype = { + initialize: function (icinga) { + this.icinga = icinga; + $(document).on('focus', 'form.gipfl-form input, form.gipfl-form textarea, form.gipfl-form select', this.formElementFocus); + $(document).on('click', '.gipfl-collapsible-control', this.toggleCollapsible); + }, + + toggleCollapsible: function (ev) { + var $toggle = $(ev.currentTarget); + var $collapsible = $toggle.parent(); + $collapsible.toggleClass('collapsed'); + }, + + formElementFocus: function (ev) { + var $input = $(ev.currentTarget); + if ($input.closest('form.editor').length) { + return; + } + var $set = $input.closest('.extensible-set'); + if ($set.length) { + var $textInputs = $('input[type=text]', $set); + if ($textInputs.length > 1) { + $textInputs.not(':first').attr('tabIndex', '-1'); + } + } + + var $dd = $input.closest('dd'); + if ($dd.attr('id') && $dd.attr('id').match(/button/)) { + return; + } + var $li = $input.closest('li'); + var $dt = $dd.prev(); + var $form = $dd.closest('form'); + + $form.find('dt, dd, dl, li').removeClass('active'); + $li.addClass('active'); + $dt.addClass('active'); + $dd.addClass('active'); + $dt.closest('dl').addClass('active'); + }, + + highlightFormErrors: function ($container) { + $container.find('dd ul.errors').each(function (idx, ul) { + var $ul = $(ul); + var $dd = $ul.closest('dd'); + var $dt = $dd.prev(); + + $dt.addClass('errors'); + $dd.addClass('errors'); + }); + }, + + toggleFieldset: function (ev) { + ev.stopPropagation(); + var $fieldset = $(ev.currentTarget).closest('fieldset'); + $fieldset.toggleClass('collapsed'); + this.fixFieldsetInfo($fieldset); + this.openedFieldsets[$fieldset.attr('id')] = ! $fieldset.hasClass('collapsed'); + } + }; + + window.incubatorComponentLoader.addComponent(new Web()); +})(window, jQuery); diff --git a/vendor/gipfl/web/src/Form.php b/vendor/gipfl/web/src/Form.php new file mode 100644 index 0000000..e5e52f9 --- /dev/null +++ b/vendor/gipfl/web/src/Form.php @@ -0,0 +1,281 @@ +hasBeenAssembled === false) { + if ($this->getRequest() === null) { + throw new RuntimeException('Cannot assemble a WebForm without a Request'); + } + $this->registerGipflElementLoader(); + $this->setupStyling(); + parent::ensureAssembled(); + $this->prepareWebForm(); + } + + return $this; + } + + protected function registerGipflElementLoader() + { + $this->addElementLoader(__NAMESPACE__ . '\\Form\\Element'); + } + + public function setSubmitted($submitted = true) + { + $this->hasBeenSubmitted = (bool) $submitted; + + return $this; + } + + public function hasBeenSubmitted() + { + if ($this->hasBeenSubmitted === null) { + return parent::hasBeenSubmitted(); + } else { + return $this->hasBeenSubmitted; + } + } + + public function disableCsrf() + { + $this->useCsrf = false; + + return $this; + } + + public function doNotCheckFormName() + { + $this->useFormName = false; + + return $this; + } + + protected function prepareWebForm() + { + if ($this->hasElement($this->formNameElementName)) { + return; // Called twice + } + if ($this->useFormName) { + $this->addFormNameElement(); + } + if ($this->useCsrf && $this->getMethod() === 'POST') { + $this->addCsrfElement(); + } + } + + protected function getUniqueFormName() + { + return get_class($this); + } + + protected function addFormNameElement() + { + $element = new HiddenElement($this->formNameElementName, [ + 'value' => $this->getUniqueFormName(), + 'ignore' => true, + ]); + $this->prepend($element); + $this->registerElement($element); + } + + public function addHidden($name, $value = null, $attributes = []) + { + if (is_array($value) && empty($attributes)) { + $attributes = $value; + $value = null; + } elseif ($value === null && is_scalar($attributes)) { + $value = $attributes; + $attributes = []; + } + if ($value !== null) { + $attributes['value'] = $value; + } + $element = new HiddenElement($name, $attributes); + $this->prepend($element); + $this->registerElement($element); + } + + public function registerElement(FormElement $element) + { + $idPrefix = ''; + if ($element instanceof BaseHtmlElement) { + if (! $element->getAttributes()->has('id')) { + $element->addAttributes(['id' => $idPrefix . $element->getName()]); + } + } + + return parent::registerElement($element); + } + + public function setElementValue($element, $value) + { + $this->wantFormElement($element)->setValue($value); + } + + public function getElementValue($elementName, $defaultValue = null) + { + $value = $this->getElement($elementName)->getValue(); + if ($value === null) { + return $defaultValue; + } else { + return $value; + } + } + + public function hasElementValue($elementName) + { + if ($this->hasElement($elementName)) { + return $this->getElement($elementName)->hasValue(); + } else { + return false; + } + } + + /** + * @param $element + * @return FormElement + */ + protected function wantFormElement($element) + { + if ($element instanceof BaseFormElement) { + return $element; + } else { + return $this->getElement($element); + } + } + + public function triggerElementError($element, $message, ...$params) + { + if (! empty($params)) { + $message = Html::sprintf($message, $params); + } + + $element = $this->wantFormElement($this->getElement($element)); + $element->addValidators([ + new AlwaysFailValidator(['message' => $message]) + ]); + } + + protected function setupStyling() + { + $this->setSeparator("\n"); + $this->addAttributes(['class' => $this->formCssClasses]); + if ($this->defaultDecoratorClass !== null) { + $this->setDefaultElementDecorator(new $this->defaultDecoratorClass); + } + } + + protected function addCsrfElement() + { + $element = new HiddenElement('__CSRF__', [ + 'ignore' => true, + ]); + $element->setValidators([ + new PhpSessionBasedCsrfTokenValidator() + ]); + // prepend / register -> avoid decorator + $this->prepend($element); + $this->registerElement($element); + if ($this->hasBeenSent()) { + if (! $element->isValid()) { + $element->setValue(PhpSessionBasedCsrfTokenValidator::generateCsrfValue()); + } + } else { + $element->setValue(PhpSessionBasedCsrfTokenValidator::generateCsrfValue()); + } + } + + public function getSentValue($name, $default = null) + { + $params = $this->getSentValues(); + + if (array_key_exists($name, $params)) { + return $params[$name]; + } else { + return $default; + } + } + + public function getSentValues() + { + $request = $this->getRequest(); + if ($request === null) { + throw new RuntimeException( + "It's impossible to access SENT values with no request" + ); + } + + if ($request->getMethod() === 'POST') { + $params = $request->getParsedBody(); + } elseif ($this->getMethod() === 'GET') { + parse_str($request->getUri()->getQuery(), $params); + } else { + $params = []; + } + + return $params; + } + + protected function onError() + { + $messages = $this->getMessages(); + if (empty($messages)) { + return; + } + $errors = []; + foreach ($this->getMessages() as $message) { + if ($message instanceof Exception) { + $this->prepend(Error::show($message)); + } else { + $errors[] = $message; + } + } + if (! empty($errors)) { + $this->prepend(Hint::error(implode(', ', $errors))); + } + } + + public function hasBeenSent() + { + if (parent::hasBeenSent()) { + return !$this->useFormName || $this->getSentValue($this->formNameElementName) + === $this->getUniqueFormName(); + } else { + return false; + } + } +} diff --git a/vendor/gipfl/web/src/Form/Decorator/DdDtDecorator.php b/vendor/gipfl/web/src/Form/Decorator/DdDtDecorator.php new file mode 100644 index 0000000..e5deae4 --- /dev/null +++ b/vendor/gipfl/web/src/Form/Decorator/DdDtDecorator.php @@ -0,0 +1,158 @@ +element = $element; + $decorator->elementDoc = new HtmlDocument(); + $decorator->elementDoc->add($element); + // if (! $element instanceof HiddenElement) { + $element->prependWrapper($decorator); + + return $decorator; + } + + protected function prepareLabel() + { + $element = $this->element; + $label = $element->getLabel(); + if ($label === null || \strlen($label) === 0) { + return null; + } + + // Set HTML element.id to element name unless defined + if ($element->getAttributes()->has('id')) { + $attributes = ['for' => $element->getAttributes()->get('id')->getValue()]; + } else { + $attributes = null; + } + + if ($element->isRequired()) { + $label = [$label, Html::tag('span', ['aria-hidden' => 'true'], '*')]; + } + + return Html::tag('label', $attributes, $label); + } + + public function getAttributes() + { + $attributes = parent::getAttributes(); + + // TODO: only when sent?! + if ($this->element->hasBeenValidated() && ! $this->element->isValid()) { + HtmlHelper::addClassOnce($attributes, static::CSS_CLASS_ELEMENT_HAS_ERRORS); + } + + return $attributes; + } + + protected function prepareDescription() + { + if ($this->element) { + $description = $this->element->getDescription(); + if ($description !== null && \strlen($description)) { + return Html::tag('p', ['class' => static::CSS_CLASS_DESCRIPTION], $description); + } + } + + return null; + } + + protected function prepareErrors() + { + $errors = []; + foreach ($this->element->getMessages() as $message) { + $errors[] = Html::tag('li', $message); + } + + if (empty($errors)) { + return null; + } else { + return Html::tag('ul', ['class' => static::CSS_CLASS_ELEMENT_ERRORS], $errors); + } + } + + public function add($content) + { + // Our wrapper implementation automatically adds the wrapped element but + // we already do so in assemble() + if ($content !== $this->element) { + parent::add($content); + } + + return $this; + } + + protected function assemble() + { + $this->add([$this->dt(), $this->dd()]); + } + + public function getElementDocument() + { + return $this->elementDoc; + } + + public function dt() + { + if ($this->dt === null) { + $this->dt = Html::tag('dt', null, $this->prepareLabel()); + } + + return $this->dt; + } + + /** + * @return \ipl\Html\HtmlElement + */ + public function dd() + { + if ($this->dd === null) { + $this->dd = Html::tag('dd', null, [ + $this->getElementDocument(), + $this->prepareErrors(), + $this->prepareDescription() + ]); + } + + return $this->dd; + } + + public function __destruct() + { + $this->wrapper = null; + } +} diff --git a/vendor/gipfl/web/src/Form/Element/Boolean.php b/vendor/gipfl/web/src/Form/Element/Boolean.php new file mode 100644 index 0000000..dc5f85f --- /dev/null +++ b/vendor/gipfl/web/src/Form/Element/Boolean.php @@ -0,0 +1,39 @@ + $this->translate('Yes'), + 'n' => $this->translate('No'), + ]; + if (! $this->isRequired()) { + $options = [ + null => $this->translate('- please choose -'), + ] + $options; + } + + $this->setOptions($options); + } + + public function setValue($value) + { + if ($value === 'y' || $value === true) { + return parent::setValue('y'); + } elseif ($value === 'n' || $value === false) { + return parent::setValue('n'); + } + + // Hint: this will fail + return parent::setValue($value); + } +} diff --git a/vendor/gipfl/web/src/Form/Element/MultiSelect.php b/vendor/gipfl/web/src/Form/Element/MultiSelect.php new file mode 100644 index 0000000..07e2e9e --- /dev/null +++ b/vendor/gipfl/web/src/Form/Element/MultiSelect.php @@ -0,0 +1,119 @@ +getAttributes()->add('multiple', true); + } + + protected function registerValueCallback(Attributes $attributes) + { + $attributes->registerAttributeCallback( + 'value', + null, + [$this, 'setValue'] + ); + } + + public function getNameAttribute() + { + return $this->getName() . '[]'; + } + + public function setValue($value) + { + if (empty($value)) { // null, '', [] + $values = []; + } else { + $values = (array) $value; + } + $invalid = []; + foreach ($values as $val) { + if ($option = $this->getOption($val)) { + if ($option->getAttributes()->has('disabled')) { + $invalid[] = $val; + } + } else { + $invalid[] = $val; + } + } + if (count($invalid) > 0) { + $this->failForValues($invalid); + return $this; + } + + $this->value = $values; + $this->valid = null; + $this->updateSelection(); + + return $this; + } + + protected function failForValues($values) + { + $this->valid = false; + if (count($values) === 1) { + $value = array_shift($values); + $this->addMessage("'$value' is not allowed here"); + } else { + $valueString = implode("', '", $values); + $this->addMessage("'$valueString' are not allowed here"); + } + } + + public function validate() + { + /** + * @TODO(lippserd): {@link SelectElement::validate()} doesn't work here because isset checks fail with + * illegal offset type errors since our value is an array. It would make sense to decouple the classes to + * avoid having to copy code from the base class. + * Also note that {@see setValue()} already performs most of the validation. + */ + if ($this->isRequired() && empty($this->getValue())) { + $this->valid = false; + } else { + /** + * Copied from {@link \ipl\Html\BaseHtmlElement::validate()}. + */ + $this->valid = $this->getValidators()->isValid($this->getValue()); + $this->addMessages($this->getValidators()->getMessages()); + } + } + + public function updateSelection() + { + foreach ($this->options as $value => $option) { + if (in_array($value, $this->value)) { + $option->getAttributes()->add('selected', true); + } else { + $option->getAttributes()->remove('selected'); + } + } + + return $this; + } + + protected function assemble() + { + foreach ($this->options as $option) { + $this->add($option); + } + } +} diff --git a/vendor/gipfl/web/src/Form/Element/Password.php b/vendor/gipfl/web/src/Form/Element/Password.php new file mode 100644 index 0000000..b6f148e --- /dev/null +++ b/vendor/gipfl/web/src/Form/Element/Password.php @@ -0,0 +1,11 @@ +elementName = $elementName; + $this->elementAttributes = $elementAttributes; + $this->buttonAttributes = $buttonAttributes; + } + + public function addToForm(Form $form) + { + $button = $this->getButton(); + $form->registerElement($button); + $element = $this->getElement(); + $form->addElement($element); + /** @var DdDtDecorator $deco */ + $deco = $element->getWrapper(); + if ($deco instanceof DdDtDecorator) { + $deco->addAttributes(['position' => 'relative'])->getElementDocument()->add($button); + } + } + + public function getElement() + { + if ($this->element === null) { + $this->element = $this->createTextElement( + $this->elementName, + $this->elementAttributes + ); + } + + return $this->element; + } + + public function getButton() + { + if ($this->button === null) { + $this->button = $this->createSubmitElement( + $this->elementName . $this->buttonSuffix, + $this->buttonAttributes + ); + } + + return $this->button; + } + + protected function createTextElement($name, $attributes = null) + { + $element = new TextElement($name, $attributes); + $element->addAttributes([ + 'class' => $this->elementClasses, + ]); + + return $element; + } + + protected function createSubmitElement($name, $attributes = null) + { + $element = new SubmitElement($name, $attributes); + $element->addAttributes([ + 'formnovalidate' => true, + 'class' => $this->buttonClasses, + ]); + + return $element; + } +} diff --git a/vendor/gipfl/web/src/Form/Feature/NextConfirmCancel.php b/vendor/gipfl/web/src/Form/Feature/NextConfirmCancel.php new file mode 100644 index 0000000..81885d6 --- /dev/null +++ b/vendor/gipfl/web/src/Form/Feature/NextConfirmCancel.php @@ -0,0 +1,153 @@ +next = $next; + $this->confirm = $confirm; + $this->cancel = $cancel; + $this->withNextContent = new HtmlDocument(); + $this->withNext = new DeferredText(function () { + return $this->withNextContent; + }); + $this->withNext->setEscaped(); + + $this->withConfirmContent = new HtmlDocument(); + $this->withConfirm = new DeferredText(function () { + return $this->withConfirmContent; + }); + $this->withConfirm->setEscaped(); + } + + public function showWithNext($content) + { + $this->withNextContent->add($content); + } + + public function showWithConfirm($content) + { + $this->withConfirmContent->add($content); + } + + /** + * @param ValidHtml $html + * @param array $found Internal parameter + * @return BaseFormElement[] + */ + protected function pickFormElements(ValidHtml $html, &$found = []) + { + if ($html instanceof BaseFormElement) { + $found[] = $html; + } elseif ($html instanceof HtmlDocument) { + foreach ($html->getContent() as $content) { + $this->pickFormElements($content, $found); + } + } + + return $found; + } + + /** + * @param string $label + * @param array $attributes + * @return SubmitElement + */ + public static function buttonNext($label, $attributes = []) + { + return new SubmitElement('next', $attributes + [ + 'label' => $label + ]); + } + + /** + * @param string $label + * @param array $attributes + * @return SubmitElement + */ + public static function buttonConfirm($label, $attributes = []) + { + return new SubmitElement('submit', $attributes + [ + 'label' => $label + ]); + } + + /** + * @param string $label + * @param array $attributes + * @return SubmitElement + */ + public static function buttonCancel($label, $attributes = []) + { + return new SubmitElement('cancel', $attributes + [ + 'label' => $label + ]); + } + + public function addToForm(Form $form) + { + $cancel = $this->cancel; + $confirm = $this->confirm; + $next = $this->next; + if ($form->hasBeenSent()) { + $form->add($this->withConfirm); + if ($this->confirmFirst) { + $form->addElement($confirm); + $form->addElement($cancel); + } else { + $form->addElement($cancel); + $form->addElement($confirm); + } + if ($cancel->hasBeenPressed()) { + $this->withConfirmContent = new HtmlDocument(); + // HINT: we might also want to redirect on cancel and stop here, + // but currently we have no Response + $form->setSubmitted(false); + $form->remove($confirm); + $form->remove($cancel); + $form->add($next); + $form->setSubmitButton($next); + } else { + $form->setSubmitButton($confirm); + $form->remove($next); + foreach ($this->pickFormElements($this->withConfirmContent) as $element) { + $form->registerElement($element); + } + } + } else { + $form->add($this->withNext); + foreach ($this->pickFormElements($this->withNextContent) as $element) { + $form->registerElement($element); + } + $form->addElement($next); + } + } +} diff --git a/vendor/gipfl/web/src/Form/Validator/AlwaysFailValidator.php b/vendor/gipfl/web/src/Form/Validator/AlwaysFailValidator.php new file mode 100644 index 0000000..6fee92f --- /dev/null +++ b/vendor/gipfl/web/src/Form/Validator/AlwaysFailValidator.php @@ -0,0 +1,16 @@ +getSetting('message'); + if ($message) { + $this->addMessage($message); + } + + return false; + } +} diff --git a/vendor/gipfl/web/src/Form/Validator/PhpSessionBasedCsrfTokenValidator.php b/vendor/gipfl/web/src/Form/Validator/PhpSessionBasedCsrfTokenValidator.php new file mode 100644 index 0000000..2f08c6c --- /dev/null +++ b/vendor/gipfl/web/src/Form/Validator/PhpSessionBasedCsrfTokenValidator.php @@ -0,0 +1,34 @@ +addMessage('An invalid CSRF token has been submitted'); + return false; + } + } + + public static function generateCsrfValue() + { + $seed = \mt_rand(); + $token = \hash('sha256', \session_id() . $seed); + + return \sprintf('%s|%s', $seed, $token); + } +} diff --git a/vendor/gipfl/web/src/Form/Validator/SimpleValidator.php b/vendor/gipfl/web/src/Form/Validator/SimpleValidator.php new file mode 100644 index 0000000..e06a10f --- /dev/null +++ b/vendor/gipfl/web/src/Form/Validator/SimpleValidator.php @@ -0,0 +1,27 @@ +settings = $settings; + } + + public function getSetting($name, $default = null) + { + if (array_key_exists($name, $this->settings)) { + return $this->settings[$name]; + } else { + return $default; + } + } +} diff --git a/vendor/gipfl/web/src/HtmlHelper.php b/vendor/gipfl/web/src/HtmlHelper.php new file mode 100644 index 0000000..19862f8 --- /dev/null +++ b/vendor/gipfl/web/src/HtmlHelper.php @@ -0,0 +1,29 @@ +getAttributes(), $class); + } + + public static function addClassOnce(Attributes $attributes, $class) + { + if (! HtmlHelper::classIsSet($attributes, $class)) { + $attributes->add('class', $class); + } + } + + public static function classIsSet(Attributes $attributes, $class) + { + $classes = $attributes->get('class'); + + return \is_array($classes) && in_array($class, $classes) + || \is_string($classes) && $classes === $class; + } +} diff --git a/vendor/gipfl/web/src/InlineForm.php b/vendor/gipfl/web/src/InlineForm.php new file mode 100644 index 0000000..fd6b301 --- /dev/null +++ b/vendor/gipfl/web/src/InlineForm.php @@ -0,0 +1,10 @@ + 'gipfl-name-value-table']; + + public static function create($pairs = []) + { + $self = new static; + $self->addNameValuePairs($pairs); + + return $self; + } + + public function createNameValueRow($name, $value) + { + return $this::tr([$this::th($name), $this::wantTd($value)]); + } + + public function addNameValueRow($name, $value) + { + return $this->add($this->createNameValueRow($name, $value)); + } + + public function addNameValuePairs($pairs) + { + foreach ($pairs as $name => $value) { + $this->addNameValueRow($name, $value); + } + + return $this; + } + + protected function wantTd($value) + { + if ($value instanceof BaseHtmlElement && $value->getTag() === 'td') { + return $value; + } else { + return $this::td($value); + } + } +} diff --git a/vendor/gipfl/web/src/Widget/CollapsibleList.php b/vendor/gipfl/web/src/Widget/CollapsibleList.php new file mode 100644 index 0000000..0df8234 --- /dev/null +++ b/vendor/gipfl/web/src/Widget/CollapsibleList.php @@ -0,0 +1,74 @@ + 'gipfl-collapsible' + ]; + + protected $defaultListAttributes; + + protected $defaultSectionAttributes; + + protected $items = []; + + public function __construct($items = [], $listAttributes = null) + { + if ($listAttributes !== null) { + $this->defaultListAttributes = $listAttributes; + } + foreach ($items as $title => $item) { + $this->addItem($title, $item); + } + } + + public function addItem($title, $content) + { + if ($this->hasItem($title)) { + throw new LogicException("Cannot add item with title '$title' twice"); + } + $item = Html::tag('li', [ + Html::tag('a', ['href' => '#', 'class' => 'gipfl-collapsible-control'], $title), + $content + ]); + + if (count($this->items) > 0) { + $item->getAttributes()->add('class', 'collapsed'); + } + $this->items[$title] = $item; + } + + public function hasItem($title) + { + return isset($this->items[$title]); + } + + public function getItem($name) + { + if (isset($this->items[$name])) { + return $this->items[$name]; + } + + throw new InvalidArgumentException("There is no '$name' item in this list"); + } + + protected function assemble() + { + if ($this->defaultListAttributes) { + $this->addAttributes($this->defaultListAttributes); + } + foreach ($this->items as $item) { + $this->add($item); + } + } +} diff --git a/vendor/gipfl/web/src/Widget/ConfigDiff.php b/vendor/gipfl/web/src/Widget/ConfigDiff.php new file mode 100644 index 0000000..8ac366f --- /dev/null +++ b/vendor/gipfl/web/src/Widget/ConfigDiff.php @@ -0,0 +1,106 @@ +vendorDir = \dirname(\dirname(__DIR__)) . '/vendor'; + require_once $this->vendorDir . '/php-diff/lib/Diff.php'; + + if (empty($a)) { + $this->a = []; + } else { + $this->a = explode("\n", (string) $a); + } + + if (empty($b)) { + $this->b = []; + } else { + $this->b = explode("\n", (string) $b); + } + + $options = [ + 'context' => 5, + // 'ignoreWhitespace' => true, + // 'ignoreCase' => true, + ]; + $this->diff = new Diff($this->a, $this->b, $options); + } + + public function render() + { + return $this->renderHtml(); + } + + /** + * @return string + */ + public function renderHtml() + { + return $this->diff->Render($this->getHtmlRenderer()); + } + + public function setHtmlRenderer($name) + { + if (in_array($name, $this->knownHtmlRenderers)) { + $this->htmlRenderer = $name; + } else { + throw new InvalidArgumentException("There is no known '$name' renderer"); + } + + return $this; + } + + protected function getHtmlRenderer() + { + $filename = sprintf( + '%s/vendor/php-diff/lib/Diff/Renderer/Html/%s.php', + $this->vendorDir, + $this->htmlRenderer + ); + require_once($filename); + + $class = 'Diff_Renderer_Html_' . $this->htmlRenderer; + + return new $class(); + } + + public function __toString() + { + return $this->renderHtml(); + } + + public static function create($a, $b) + { + return new static($a, $b); + } +} diff --git a/vendor/gipfl/web/src/Widget/Hint.php b/vendor/gipfl/web/src/Widget/Hint.php new file mode 100644 index 0000000..785d9e4 --- /dev/null +++ b/vendor/gipfl/web/src/Widget/Hint.php @@ -0,0 +1,45 @@ + 'gipfl-widget-hint' + ]; + + public function __construct($message, $class = 'ok', ...$params) + { + $this->addAttributes(['class' => $class]); + if (empty($params)) { + $this->setContent($message); + } else { + $this->setContent(Html::sprintf($message, ...$params)); + } + } + + public static function ok($message, ...$params) + { + return new static($message, 'ok', ...$params); + } + + public static function info($message, ...$params) + { + return new static($message, 'info', ...$params); + } + + public static function warning($message, ...$params) + { + return new static($message, 'warning', ...$params); + } + + public static function error($message, ...$params) + { + return new static($message, 'error', ...$params); + } +} diff --git a/vendor/gipfl/web/src/vendor/php-diff/lib/Diff.php b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff.php new file mode 100644 index 0000000..d1eb9da --- /dev/null +++ b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff.php @@ -0,0 +1,179 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Chris Boulton nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package Diff + * @author Chris Boulton + * @copyright (c) 2009 Chris Boulton + * @license New BSD License http://www.opensource.org/licenses/bsd-license.php + * @version 1.1 + * @link http://github.com/chrisboulton/php-diff + */ + +class Diff +{ + /** + * @var array The "old" sequence to use as the basis for the comparison. + */ + private $a = null; + + /** + * @var array The "new" sequence to generate the changes for. + */ + private $b = null; + + /** + * @var array Array containing the generated opcodes for the differences between the two items. + */ + private $groupedCodes = null; + + /** + * @var array Associative array of the default options available for the diff class and their default value. + */ + private $defaultOptions = array( + 'context' => 3, + 'ignoreNewLines' => false, + 'ignoreWhitespace' => false, + 'ignoreCase' => false + ); + + /** + * @var array Array of the options that have been applied for generating the diff. + */ + private $options = array(); + + /** + * The constructor. + * + * @param array $a Array containing the lines of the first string to compare. + * @param array $b Array containing the lines for the second string to compare. + */ + public function __construct($a, $b, $options=array()) + { + $this->a = $a; + $this->b = $b; + + if (is_array($options)) + $this->options = array_merge($this->defaultOptions, $options); + else + $this->options = $this->defaultOptions; + } + + /** + * Render a diff using the supplied rendering class and return it. + * + * @param object $renderer An instance of the rendering object to use for generating the diff. + * @return mixed The generated diff. Exact return value depends on the rendered. + */ + public function render(Diff_Renderer_Abstract $renderer) + { + $renderer->diff = $this; + return $renderer->render(); + } + + /** + * Get a range of lines from $start to $end from the first comparison string + * and return them as an array. If no values are supplied, the entire string + * is returned. It's also possible to specify just one line to return only + * that line. + * + * @param int $start The starting number. + * @param int $end The ending number. If not supplied, only the item in $start will be returned. + * @return array Array of all of the lines between the specified range. + */ + public function getA($start=0, $end=null) + { + if($start == 0 && $end === null) { + return $this->a; + } + + if($end === null) { + $length = 1; + } + else { + $length = $end - $start; + } + + return array_slice($this->a, $start, $length); + + } + + /** + * Get a range of lines from $start to $end from the second comparison string + * and return them as an array. If no values are supplied, the entire string + * is returned. It's also possible to specify just one line to return only + * that line. + * + * @param int $start The starting number. + * @param int $end The ending number. If not supplied, only the item in $start will be returned. + * @return array Array of all of the lines between the specified range. + */ + public function getB($start=0, $end=null) + { + if($start == 0 && $end === null) { + return $this->b; + } + + if($end === null) { + $length = 1; + } + else { + $length = $end - $start; + } + + return array_slice($this->b, $start, $length); + } + + /** + * Generate a list of the compiled and grouped opcodes for the differences between the + * two strings. Generally called by the renderer, this class instantiates the sequence + * matcher and performs the actual diff generation and return an array of the opcodes + * for it. Once generated, the results are cached in the diff class instance. + * + * @return array Array of the grouped opcodes for the generated diff. + */ + public function getGroupedOpcodes() + { + if(!is_null($this->groupedCodes)) { + return $this->groupedCodes; + } + + require_once dirname(__FILE__).'/Diff/SequenceMatcher.php'; + $sequenceMatcher = new Diff_SequenceMatcher($this->a, $this->b, null, $this->options); + $this->groupedCodes = $sequenceMatcher->getGroupedOpcodes($this->options['context']); + return $this->groupedCodes; + } +} \ No newline at end of file diff --git a/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Abstract.php b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Abstract.php new file mode 100644 index 0000000..f63c3e7 --- /dev/null +++ b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Abstract.php @@ -0,0 +1,82 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Chris Boulton nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package DiffLib + * @author Chris Boulton + * @copyright (c) 2009 Chris Boulton + * @license New BSD License http://www.opensource.org/licenses/bsd-license.php + * @version 1.1 + * @link http://github.com/chrisboulton/php-diff + */ + +abstract class Diff_Renderer_Abstract +{ + /** + * @var object Instance of the diff class that this renderer is generating the rendered diff for. + */ + public $diff; + + /** + * @var array Array of the default options that apply to this renderer. + */ + protected $defaultOptions = array(); + + /** + * @var array Array containing the user applied and merged default options for the renderer. + */ + protected $options = array(); + + /** + * The constructor. Instantiates the rendering engine and if options are passed, + * sets the options for the renderer. + * + * @param array $options Optionally, an array of the options for the renderer. + */ + public function __construct(array $options = array()) + { + $this->setOptions($options); + } + + /** + * Set the options of the renderer to those supplied in the passed in array. + * Options are merged with the default to ensure that there aren't any missing + * options. + * + * @param array $options Array of options to set. + */ + public function setOptions(array $options) + { + $this->options = array_merge($this->defaultOptions, $options); + } +} \ No newline at end of file diff --git a/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Array.php b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Array.php new file mode 100644 index 0000000..2fe9625 --- /dev/null +++ b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Array.php @@ -0,0 +1,230 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Chris Boulton nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package DiffLib + * @author Chris Boulton + * @copyright (c) 2009 Chris Boulton + * @license New BSD License http://www.opensource.org/licenses/bsd-license.php + * @version 1.1 + * @link http://github.com/chrisboulton/php-diff + */ + +require_once dirname(__FILE__).'/../Abstract.php'; + +class Diff_Renderer_Html_Array extends Diff_Renderer_Abstract +{ + /** + * @var array Array of the default options that apply to this renderer. + */ + protected $defaultOptions = array( + 'tabSize' => 4 + ); + + /** + * Render and return an array structure suitable for generating HTML + * based differences. Generally called by subclasses that generate a + * HTML based diff and return an array of the changes to show in the diff. + * + * @return array An array of the generated chances, suitable for presentation in HTML. + */ + public function render() + { + // As we'll be modifying a & b to include our change markers, + // we need to get the contents and store them here. That way + // we're not going to destroy the original data + $a = $this->diff->getA(); + $b = $this->diff->getB(); + + $changes = array(); + $opCodes = $this->diff->getGroupedOpcodes(); + foreach($opCodes as $group) { + $blocks = array(); + $lastTag = null; + $lastBlock = 0; + foreach($group as $code) { + list($tag, $i1, $i2, $j1, $j2) = $code; + + if($tag == 'replace' && $i2 - $i1 == $j2 - $j1) { + for($i = 0; $i < ($i2 - $i1); ++$i) { + $fromLine = $a[$i1 + $i]; + $toLine = $b[$j1 + $i]; + + list($start, $end) = $this->getChangeExtent($fromLine, $toLine); + if($start != 0 || $end != 0) { + $last = $end + strlen($fromLine); + $fromLine = substr_replace($fromLine, "\0", $start, 0); + $fromLine = substr_replace($fromLine, "\1", $last + 1, 0); + $last = $end + strlen($toLine); + $toLine = substr_replace($toLine, "\0", $start, 0); + $toLine = substr_replace($toLine, "\1", $last + 1, 0); + $a[$i1 + $i] = $fromLine; + $b[$j1 + $i] = $toLine; + } + } + } + + if($tag != $lastTag) { + $blocks[] = array( + 'tag' => $tag, + 'base' => array( + 'offset' => $i1, + 'lines' => array() + ), + 'changed' => array( + 'offset' => $j1, + 'lines' => array() + ) + ); + $lastBlock = count($blocks)-1; + } + + $lastTag = $tag; + + if($tag == 'equal') { + $lines = array_slice($a, $i1, ($i2 - $i1)); + $blocks[$lastBlock]['base']['lines'] += $this->formatLines($lines); + $lines = array_slice($b, $j1, ($j2 - $j1)); + $blocks[$lastBlock]['changed']['lines'] += $this->formatLines($lines); + } + else { + if($tag == 'replace' || $tag == 'delete') { + $lines = array_slice($a, $i1, ($i2 - $i1)); + $lines = $this->formatLines($lines); + $lines = str_replace(array("\0", "\1"), array('', ''), $lines); + $blocks[$lastBlock]['base']['lines'] += $lines; + } + + if($tag == 'replace' || $tag == 'insert') { + $lines = array_slice($b, $j1, ($j2 - $j1)); + $lines = $this->formatLines($lines); + $lines = str_replace(array("\0", "\1"), array('', ''), $lines); + $blocks[$lastBlock]['changed']['lines'] += $lines; + } + } + } + $changes[] = $blocks; + } + return $changes; + } + + /** + * Given two strings, determine where the changes in the two strings + * begin, and where the changes in the two strings end. + * + * @param string $fromLine The first string. + * @param string $toLine The second string. + * @return array Array containing the starting position (0 by default) and the ending position (-1 by default) + */ + private function getChangeExtent($fromLine, $toLine) + { + $start = 0; + $limit = min(strlen($fromLine), strlen($toLine)); + while($start < $limit && $fromLine{$start} == $toLine{$start}) { + ++$start; + } + $end = -1; + $limit = $limit - $start; + while(-$end <= $limit && substr($fromLine, $end, 1) == substr($toLine, $end, 1)) { + --$end; + } + return array( + $start, + $end + 1 + ); + } + + /** + * Format a series of lines suitable for output in a HTML rendered diff. + * This involves replacing tab characters with spaces, making the HTML safe + * for output, ensuring that double spaces are replaced with   etc. + * + * @param array $lines Array of lines to format. + * @return array Array of the formatted lines. + */ + protected function formatLines($lines) + { + $lines = array_map(array($this, 'ExpandTabs'), $lines); + $lines = array_map(array($this, 'HtmlSafe'), $lines); + foreach($lines as &$line) { + $line = preg_replace_callback('# ( +)|^ #', array($this, 'fixSpaces'), $line); + } + return $lines; + } + + /** + * Replace a string containing spaces with a HTML representation using  . + * + * @param string[] $matches Array with preg matches. + * @return string The HTML representation of the string. + */ + private function fixSpaces(array $matches) + { + $count = 0; + + if (count($matches) > 1) { + $spaces = $matches[1]; + $count = strlen($spaces); + } + + if ($count == 0) { + return ''; + } + + $div = floor($count / 2); + $mod = $count % 2; + return str_repeat('  ', $div).str_repeat(' ', $mod); + } + + /** + * Replace tabs in a single line with a number of spaces as defined by the tabSize option. + * + * @param string $line The containing tabs to convert. + * @return string The line with the tabs converted to spaces. + */ + private function expandTabs($line) + { + return str_replace("\t", str_repeat(' ', $this->options['tabSize']), $line); + } + + /** + * Make a string containing HTML safe for output on a page. + * + * @param string $string The string. + * @return string The string with the HTML characters replaced by entities. + */ + private function htmlSafe($string) + { + return htmlspecialchars($string, ENT_NOQUOTES, 'UTF-8'); + } +} diff --git a/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Inline.php b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Inline.php new file mode 100644 index 0000000..a37fec6 --- /dev/null +++ b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Inline.php @@ -0,0 +1,143 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Chris Boulton nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package DiffLib + * @author Chris Boulton + * @copyright (c) 2009 Chris Boulton + * @license New BSD License http://www.opensource.org/licenses/bsd-license.php + * @version 1.1 + * @link http://github.com/chrisboulton/php-diff + */ + +require_once dirname(__FILE__).'/Array.php'; + +class Diff_Renderer_Html_Inline extends Diff_Renderer_Html_Array +{ + /** + * Render a and return diff with changes between the two sequences + * displayed inline (under each other) + * + * @return string The generated inline diff. + */ + public function render() + { + $changes = parent::render(); + $html = ''; + if(empty($changes)) { + return $html; + } + + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + foreach($changes as $i => $blocks) { + // If this is a separate block, we're condensing code so output ..., + // indicating a significant portion of the code has been collapsed as + // it is the same + if($i > 0) { + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + + foreach($blocks as $change) { + $html .= ''; + // Equal changes should be shown on both sides of the diff + if($change['tag'] == 'equal') { + foreach($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $toLine = $change['changed']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + } + // Added lines only on the right side + else if($change['tag'] == 'insert') { + foreach($change['changed']['lines'] as $no => $line) { + $toLine = $change['changed']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + } + // Show deleted lines only on the left side + else if($change['tag'] == 'delete') { + foreach($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + } + // Show modified lines on both sides + else if($change['tag'] == 'replace') { + foreach($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + + foreach($change['changed']['lines'] as $no => $line) { + $toLine = $change['changed']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + } + $html .= ''; + } + } + $html .= '
OldNewDifferences
 
'.$fromLine.''.$toLine.''.$line.'
 '.$toLine.''.$line.' 
'.$fromLine.' '.$line.' 
'.$fromLine.' '.$line.'
 '.$toLine.''.$line.'
'; + return $html; + } +} diff --git a/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/SideBySide.php b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/SideBySide.php new file mode 100644 index 0000000..307af1c --- /dev/null +++ b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/SideBySide.php @@ -0,0 +1,163 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Chris Boulton nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package DiffLib + * @author Chris Boulton + * @copyright (c) 2009 Chris Boulton + * @license New BSD License http://www.opensource.org/licenses/bsd-license.php + * @version 1.1 + * @link http://github.com/chrisboulton/php-diff + */ + +require_once dirname(__FILE__).'/Array.php'; + +class Diff_Renderer_Html_SideBySide extends Diff_Renderer_Html_Array +{ + /** + * Render a and return diff with changes between the two sequences + * displayed side by side. + * + * @return string The generated side by side diff. + */ + public function render() + { + $changes = parent::render(); + + $html = ''; + if(empty($changes)) { + return $html; + } + + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + foreach($changes as $i => $blocks) { + if($i > 0) { + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + + foreach($blocks as $change) { + $html .= ''; + // Equal changes should be shown on both sides of the diff + if($change['tag'] == 'equal') { + foreach($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $toLine = $change['changed']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + } + // Added lines only on the right side + else if($change['tag'] == 'insert') { + foreach($change['changed']['lines'] as $no => $line) { + $toLine = $change['changed']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + } + // Show deleted lines only on the left side + else if($change['tag'] == 'delete') { + foreach($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + } + // Show modified lines on both sides + else if($change['tag'] == 'replace') { + if(count($change['base']['lines']) >= count($change['changed']['lines'])) { + foreach($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + if(!isset($change['changed']['lines'][$no])) { + $toLine = ' '; + $changedLine = ' '; + } + else { + $toLine = $change['base']['offset'] + $no + 1; + $changedLine = ''.$change['changed']['lines'][$no].''; + } + $html .= ''; + $html .= ''; + $html .= ''; + } + } + else { + foreach($change['changed']['lines'] as $no => $changedLine) { + if(!isset($change['base']['lines'][$no])) { + $fromLine = ' '; + $line = ' '; + } + else { + $fromLine = $change['base']['offset'] + $no + 1; + $line = ''.$change['base']['lines'][$no].''; + } + $html .= ''; + $html .= ''; + $html .= ''; + $toLine = $change['changed']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + } + } + } + $html .= ''; + } + } + $html .= '
Old VersionNew Version
  
'.$fromLine.''.$line.' '.$toLine.''.$line.' 
  '.$toLine.''.$line.' 
'.$fromLine.''.$line.'   
'.$fromLine.''.$line.' '.$toLine.''.$changedLine.'
'.$fromLine.''.$line.' '.$toLine.''.$changedLine.'
'; + return $html; + } +} \ No newline at end of file diff --git a/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Context.php b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Context.php new file mode 100644 index 0000000..1200b01 --- /dev/null +++ b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Context.php @@ -0,0 +1,128 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Chris Boulton nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package DiffLib + * @author Chris Boulton + * @copyright (c) 2009 Chris Boulton + * @license New BSD License http://www.opensource.org/licenses/bsd-license.php + * @version 1.1 + * @link http://github.com/chrisboulton/php-diff + */ + +require_once dirname(__FILE__).'/../Abstract.php'; + +class Diff_Renderer_Text_Context extends Diff_Renderer_Abstract +{ + /** + * @var array Array of the different opcode tags and how they map to the context diff equivalent. + */ + private $tagMap = array( + 'insert' => '+', + 'delete' => '-', + 'replace' => '!', + 'equal' => ' ' + ); + + /** + * Render and return a context formatted (old school!) diff file. + * + * @return string The generated context diff. + */ + public function render() + { + $diff = ''; + $opCodes = $this->diff->getGroupedOpcodes(); + foreach($opCodes as $group) { + $diff .= "***************\n"; + $lastItem = count($group)-1; + $i1 = $group[0][1]; + $i2 = $group[$lastItem][2]; + $j1 = $group[0][3]; + $j2 = $group[$lastItem][4]; + + if($i2 - $i1 >= 2) { + $diff .= '*** '.($group[0][1] + 1).','.$i2." ****\n"; + } + else { + $diff .= '*** '.$i2." ****\n"; + } + + if($j2 - $j1 >= 2) { + $separator = '--- '.($j1 + 1).','.$j2." ----\n"; + } + else { + $separator = '--- '.$j2." ----\n"; + } + + $hasVisible = false; + foreach($group as $code) { + if($code[0] == 'replace' || $code[0] == 'delete') { + $hasVisible = true; + break; + } + } + + if($hasVisible) { + foreach($group as $code) { + list($tag, $i1, $i2, $j1, $j2) = $code; + if($tag == 'insert') { + continue; + } + $diff .= $this->tagMap[$tag].' '.implode("\n".$this->tagMap[$tag].' ', $this->diff->GetA($i1, $i2))."\n"; + } + } + + $hasVisible = false; + foreach($group as $code) { + if($code[0] == 'replace' || $code[0] == 'insert') { + $hasVisible = true; + break; + } + } + + $diff .= $separator; + + if($hasVisible) { + foreach($group as $code) { + list($tag, $i1, $i2, $j1, $j2) = $code; + if($tag == 'delete') { + continue; + } + $diff .= $this->tagMap[$tag].' '.implode("\n".$this->tagMap[$tag].' ', $this->diff->GetB($j1, $j2))."\n"; + } + } + } + return $diff; + } +} \ No newline at end of file diff --git a/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Unified.php b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Unified.php new file mode 100644 index 0000000..e94d951 --- /dev/null +++ b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Unified.php @@ -0,0 +1,87 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Chris Boulton nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package DiffLib + * @author Chris Boulton + * @copyright (c) 2009 Chris Boulton + * @license New BSD License http://www.opensource.org/licenses/bsd-license.php + * @version 1.1 + * @link http://github.com/chrisboulton/php-diff + */ + +require_once dirname(__FILE__).'/../Abstract.php'; + +class Diff_Renderer_Text_Unified extends Diff_Renderer_Abstract +{ + /** + * Render and return a unified diff. + * + * @return string The unified diff. + */ + public function render() + { + $diff = ''; + $opCodes = $this->diff->getGroupedOpcodes(); + foreach($opCodes as $group) { + $lastItem = count($group)-1; + $i1 = $group[0][1]; + $i2 = $group[$lastItem][2]; + $j1 = $group[0][3]; + $j2 = $group[$lastItem][4]; + + if($i1 == 0 && $i2 == 0) { + $i1 = -1; + $i2 = -1; + } + + $diff .= '@@ -'.($i1 + 1).','.($i2 - $i1).' +'.($j1 + 1).','.($j2 - $j1)." @@\n"; + foreach($group as $code) { + list($tag, $i1, $i2, $j1, $j2) = $code; + if($tag == 'equal') { + $diff .= ' '.implode("\n ", $this->diff->GetA($i1, $i2))."\n"; + } + else { + if($tag == 'replace' || $tag == 'delete') { + $diff .= '-'.implode("\n-", $this->diff->GetA($i1, $i2))."\n"; + } + + if($tag == 'replace' || $tag == 'insert') { + $diff .= '+'.implode("\n+", $this->diff->GetB($j1, $j2))."\n"; + } + } + } + } + return $diff; + } +} \ No newline at end of file diff --git a/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/SequenceMatcher.php b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/SequenceMatcher.php new file mode 100644 index 0000000..a289e39 --- /dev/null +++ b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/SequenceMatcher.php @@ -0,0 +1,742 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Chris Boulton nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package Diff + * @author Chris Boulton + * @copyright (c) 2009 Chris Boulton + * @license New BSD License http://www.opensource.org/licenses/bsd-license.php + * @version 1.1 + * @link http://github.com/chrisboulton/php-diff + */ + +class Diff_SequenceMatcher +{ + /** + * @var string|array Either a string or an array containing a callback function to determine if a line is "junk" or not. + */ + private $junkCallback = null; + + /** + * @var array The first sequence to compare against. + */ + private $a = array(); + + /** + * @var array The second sequence. + */ + private $b = array(); + + /** + * @var array Array of characters that are considered junk from the second sequence. Characters are the array key. + */ + private $junkDict = array(); + + /** + * @var array Array of indices that do not contain junk elements. + */ + private $b2j = array(); + + private $options = array(); + + private $defaultOptions = array( + 'ignoreNewLines' => false, + 'ignoreWhitespace' => false, + 'ignoreCase' => false + ); + + /** + * The constructor. With the sequences being passed, they'll be set for the + * sequence matcher and it will perform a basic cleanup & calculate junk + * elements. + * + * @param string|array $a A string or array containing the lines to compare against. + * @param string|array $b A string or array containing the lines to compare. + * @param string|array $junkCallback Either an array or string that references a callback function (if there is one) to determine 'junk' characters. + */ + public function __construct($a, $b, $junkCallback=null, $options) + { + $this->a = array(); + $this->b = array(); + $this->junkCallback = $junkCallback; + $this->setOptions($options); + $this->setSequences($a, $b); + } + + public function setOptions($options) + { + $this->options = array_merge($this->defaultOptions, $options); + } + + /** + * Set the first and second sequences to use with the sequence matcher. + * + * @param string|array $a A string or array containing the lines to compare against. + * @param string|array $b A string or array containing the lines to compare. + */ + public function setSequences($a, $b) + { + $this->setSeq1($a); + $this->setSeq2($b); + } + + /** + * Set the first sequence ($a) and reset any internal caches to indicate that + * when calling the calculation methods, we need to recalculate them. + * + * @param string|array $a The sequence to set as the first sequence. + */ + public function setSeq1($a) + { + if(!is_array($a)) { + $a = str_split($a); + } + if($a == $this->a) { + return; + } + + $this->a= $a; + $this->matchingBlocks = null; + $this->opCodes = null; + } + + /** + * Set the second sequence ($b) and reset any internal caches to indicate that + * when calling the calculation methods, we need to recalculate them. + * + * @param string|array $b The sequence to set as the second sequence. + */ + public function setSeq2($b) + { + if(!is_array($b)) { + $b = str_split($b); + } + if($b == $this->b) { + return; + } + + $this->b = $b; + $this->matchingBlocks = null; + $this->opCodes = null; + $this->fullBCount = null; + $this->chainB(); + } + + /** + * Generate the internal arrays containing the list of junk and non-junk + * characters for the second ($b) sequence. + */ + private function chainB() + { + $length = count ($this->b); + $this->b2j = array(); + $popularDict = array(); + + for($i = 0; $i < $length; ++$i) { + $char = $this->b[$i]; + if(isset($this->b2j[$char])) { + if($length >= 200 && count($this->b2j[$char]) * 100 > $length) { + $popularDict[$char] = 1; + unset($this->b2j[$char]); + } + else { + $this->b2j[$char][] = $i; + } + } + else { + $this->b2j[$char] = array( + $i + ); + } + } + + // Remove leftovers + foreach(array_keys($popularDict) as $char) { + unset($this->b2j[$char]); + } + + $this->junkDict = array(); + if(is_callable($this->junkCallback)) { + foreach(array_keys($popularDict) as $char) { + if(call_user_func($this->junkCallback, $char)) { + $this->junkDict[$char] = 1; + unset($popularDict[$char]); + } + } + + foreach(array_keys($this->b2j) as $char) { + if(call_user_func($this->junkCallback, $char)) { + $this->junkDict[$char] = 1; + unset($this->b2j[$char]); + } + } + } + } + + /** + * Checks if a particular character is in the junk dictionary + * for the list of junk characters. + * + * @return boolean $b True if the character is considered junk. False if not. + */ + private function isBJunk($b) + { + if(isset($this->juncDict[$b])) { + return true; + } + + return false; + } + + /** + * Find the longest matching block in the two sequences, as defined by the + * lower and upper constraints for each sequence. (for the first sequence, + * $alo - $ahi and for the second sequence, $blo - $bhi) + * + * Essentially, of all of the maximal matching blocks, return the one that + * startest earliest in $a, and all of those maximal matching blocks that + * start earliest in $a, return the one that starts earliest in $b. + * + * If the junk callback is defined, do the above but with the restriction + * that the junk element appears in the block. Extend it as far as possible + * by matching only junk elements in both $a and $b. + * + * @param int $alo The lower constraint for the first sequence. + * @param int $ahi The upper constraint for the first sequence. + * @param int $blo The lower constraint for the second sequence. + * @param int $bhi The upper constraint for the second sequence. + * @return array Array containing the longest match that includes the starting position in $a, start in $b and the length/size. + */ + public function findLongestMatch($alo, $ahi, $blo, $bhi) + { + $a = $this->a; + $b = $this->b; + + $bestI = $alo; + $bestJ = $blo; + $bestSize = 0; + + $j2Len = array(); + $nothing = array(); + + for($i = $alo; $i < $ahi; ++$i) { + $newJ2Len = array(); + $jDict = $this->arrayGetDefault($this->b2j, $a[$i], $nothing); + foreach($jDict as $jKey => $j) { + if($j < $blo) { + continue; + } + else if($j >= $bhi) { + break; + } + + $k = $this->arrayGetDefault($j2Len, $j -1, 0) + 1; + $newJ2Len[$j] = $k; + if($k > $bestSize) { + $bestI = $i - $k + 1; + $bestJ = $j - $k + 1; + $bestSize = $k; + } + } + + $j2Len = $newJ2Len; + } + + while($bestI > $alo && $bestJ > $blo && !$this->isBJunk($b[$bestJ - 1]) && + !$this->linesAreDifferent($bestI - 1, $bestJ - 1)) { + --$bestI; + --$bestJ; + ++$bestSize; + } + + while($bestI + $bestSize < $ahi && ($bestJ + $bestSize) < $bhi && + !$this->isBJunk($b[$bestJ + $bestSize]) && !$this->linesAreDifferent($bestI + $bestSize, $bestJ + $bestSize)) { + ++$bestSize; + } + + while($bestI > $alo && $bestJ > $blo && $this->isBJunk($b[$bestJ - 1]) && + !$this->isLineDifferent($bestI - 1, $bestJ - 1)) { + --$bestI; + --$bestJ; + ++$bestSize; + } + + while($bestI + $bestSize < $ahi && $bestJ + $bestSize < $bhi && + $this->isBJunk($b[$bestJ + $bestSize]) && !$this->linesAreDifferent($bestI + $bestSize, $bestJ + $bestSize)) { + ++$bestSize; + } + + return array( + $bestI, + $bestJ, + $bestSize + ); + } + + /** + * Check if the two lines at the given indexes are different or not. + * + * @param int $aIndex Line number to check against in a. + * @param int $bIndex Line number to check against in b. + * @return boolean True if the lines are different and false if not. + */ + public function linesAreDifferent($aIndex, $bIndex) + { + $lineA = $this->a[$aIndex]; + $lineB = $this->b[$bIndex]; + + if($this->options['ignoreWhitespace']) { + $replace = array("\t", ' '); + $lineA = str_replace($replace, '', $lineA); + $lineB = str_replace($replace, '', $lineB); + } + + if($this->options['ignoreCase']) { + $lineA = strtolower($lineA); + $lineB = strtolower($lineB); + } + + if($lineA != $lineB) { + return true; + } + + return false; + } + + /** + * Return a nested set of arrays for all of the matching sub-sequences + * in the strings $a and $b. + * + * Each block contains the lower constraint of the block in $a, the lower + * constraint of the block in $b and finally the number of lines that the + * block continues for. + * + * @return array Nested array of the matching blocks, as described by the function. + */ + public function getMatchingBlocks() + { + if(!empty($this->matchingBlocks)) { + return $this->matchingBlocks; + } + + $aLength = count($this->a); + $bLength = count($this->b); + + $queue = array( + array( + 0, + $aLength, + 0, + $bLength + ) + ); + + $matchingBlocks = array(); + while(!empty($queue)) { + list($alo, $ahi, $blo, $bhi) = array_pop($queue); + $x = $this->findLongestMatch($alo, $ahi, $blo, $bhi); + list($i, $j, $k) = $x; + if($k) { + $matchingBlocks[] = $x; + if($alo < $i && $blo < $j) { + $queue[] = array( + $alo, + $i, + $blo, + $j + ); + } + + if($i + $k < $ahi && $j + $k < $bhi) { + $queue[] = array( + $i + $k, + $ahi, + $j + $k, + $bhi + ); + } + } + } + + usort($matchingBlocks, array($this, 'tupleSort')); + + $i1 = 0; + $j1 = 0; + $k1 = 0; + $nonAdjacent = array(); + foreach($matchingBlocks as $block) { + list($i2, $j2, $k2) = $block; + if($i1 + $k1 == $i2 && $j1 + $k1 == $j2) { + $k1 += $k2; + } + else { + if($k1) { + $nonAdjacent[] = array( + $i1, + $j1, + $k1 + ); + } + + $i1 = $i2; + $j1 = $j2; + $k1 = $k2; + } + } + + if($k1) { + $nonAdjacent[] = array( + $i1, + $j1, + $k1 + ); + } + + $nonAdjacent[] = array( + $aLength, + $bLength, + 0 + ); + + $this->matchingBlocks = $nonAdjacent; + return $this->matchingBlocks; + } + + /** + * Return a list of all of the opcodes for the differences between the + * two strings. + * + * The nested array returned contains an array describing the opcode + * which includes: + * 0 - The type of tag (as described below) for the opcode. + * 1 - The beginning line in the first sequence. + * 2 - The end line in the first sequence. + * 3 - The beginning line in the second sequence. + * 4 - The end line in the second sequence. + * + * The different types of tags include: + * replace - The string from $i1 to $i2 in $a should be replaced by + * the string in $b from $j1 to $j2. + * delete - The string in $a from $i1 to $j2 should be deleted. + * insert - The string in $b from $j1 to $j2 should be inserted at + * $i1 in $a. + * equal - The two strings with the specified ranges are equal. + * + * @return array Array of the opcodes describing the differences between the strings. + */ + public function getOpCodes() + { + if(!empty($this->opCodes)) { + return $this->opCodes; + } + + $i = 0; + $j = 0; + $this->opCodes = array(); + + $blocks = $this->getMatchingBlocks(); + foreach($blocks as $block) { + list($ai, $bj, $size) = $block; + $tag = ''; + if($i < $ai && $j < $bj) { + $tag = 'replace'; + } + else if($i < $ai) { + $tag = 'delete'; + } + else if($j < $bj) { + $tag = 'insert'; + } + + if($tag) { + $this->opCodes[] = array( + $tag, + $i, + $ai, + $j, + $bj + ); + } + + $i = $ai + $size; + $j = $bj + $size; + + if($size) { + $this->opCodes[] = array( + 'equal', + $ai, + $i, + $bj, + $j + ); + } + } + return $this->opCodes; + } + + /** + * Return a series of nested arrays containing different groups of generated + * opcodes for the differences between the strings with up to $context lines + * of surrounding content. + * + * Essentially what happens here is any big equal blocks of strings are stripped + * out, the smaller subsets of changes are then arranged in to their groups. + * This means that the sequence matcher and diffs do not need to include the full + * content of the different files but can still provide context as to where the + * changes are. + * + * @param int $context The number of lines of context to provide around the groups. + * @return array Nested array of all of the grouped opcodes. + */ + public function getGroupedOpcodes($context=3) + { + $opCodes = $this->getOpCodes(); + if(empty($opCodes)) { + $opCodes = array( + array( + 'equal', + 0, + 1, + 0, + 1 + ) + ); + } + + if($opCodes[0][0] == 'equal') { + $opCodes[0] = array( + $opCodes[0][0], + max($opCodes[0][1], $opCodes[0][2] - $context), + $opCodes[0][2], + max($opCodes[0][3], $opCodes[0][4] - $context), + $opCodes[0][4] + ); + } + + $lastItem = count($opCodes) - 1; + if($opCodes[$lastItem][0] == 'equal') { + list($tag, $i1, $i2, $j1, $j2) = $opCodes[$lastItem]; + $opCodes[$lastItem] = array( + $tag, + $i1, + min($i2, $i1 + $context), + $j1, + min($j2, $j1 + $context) + ); + } + + $maxRange = $context * 2; + $groups = array(); + $group = array(); + foreach($opCodes as $code) { + list($tag, $i1, $i2, $j1, $j2) = $code; + if($tag == 'equal' && $i2 - $i1 > $maxRange) { + $group[] = array( + $tag, + $i1, + min($i2, $i1 + $context), + $j1, + min($j2, $j1 + $context) + ); + $groups[] = $group; + $group = array(); + $i1 = max($i1, $i2 - $context); + $j1 = max($j1, $j2 - $context); + } + $group[] = array( + $tag, + $i1, + $i2, + $j1, + $j2 + ); + } + + if(!empty($group) && !(count($group) == 1 && $group[0][0] == 'equal')) { + $groups[] = $group; + } + + return $groups; + } + + /** + * Return a measure of the similarity between the two sequences. + * This will be a float value between 0 and 1. + * + * Out of all of the ratio calculation functions, this is the most + * expensive to call if getMatchingBlocks or getOpCodes is yet to be + * called. The other calculation methods (quickRatio and realquickRatio) + * can be used to perform quicker calculations but may be less accurate. + * + * The ratio is calculated as (2 * number of matches) / total number of + * elements in both sequences. + * + * @return float The calculated ratio. + */ + public function Ratio() + { + $matches = array_reduce($this->getMatchingBlocks(), array($this, 'ratioReduce'), 0); + return $this->calculateRatio($matches, count ($this->a) + count ($this->b)); + } + + /** + * Helper function to calculate the number of matches for Ratio(). + * + * @param int $sum The running total for the number of matches. + * @param array $triple Array containing the matching block triple to add to the running total. + * @return int The new running total for the number of matches. + */ + private function ratioReduce($sum, $triple) + { + return $sum + ($triple[count($triple) - 1]); + } + + /** + * Quickly return an upper bound ratio for the similarity of the strings. + * This is quicker to compute than Ratio(). + * + * @return float The calculated ratio. + */ + private function quickRatio() + { + if($this->fullBCount === null) { + $this->fullBCount = array(); + $bLength = count ($b); + for($i = 0; $i < $bLength; ++$i) { + $char = $this->b[$i]; + $this->fullBCount[$char] = $this->arrayGetDefault($this->fullBCount, $char, 0) + 1; + } + } + + $avail = array(); + $matches = 0; + $aLength = count ($this->a); + for($i = 0; $i < $aLength; ++$i) { + $char = $this->a[$i]; + if(isset($avail[$char])) { + $numb = $avail[$char]; + } + else { + $numb = $this->arrayGetDefault($this->fullBCount, $char, 0); + } + $avail[$char] = $numb - 1; + if($numb > 0) { + ++$matches; + } + } + + $this->calculateRatio($matches, count ($this->a) + count ($this->b)); + } + + /** + * Return an upper bound ratio really quickly for the similarity of the strings. + * This is quicker to compute than Ratio() and quickRatio(). + * + * @return float The calculated ratio. + */ + private function realquickRatio() + { + $aLength = count ($this->a); + $bLength = count ($this->b); + + return $this->calculateRatio(min($aLength, $bLength), $aLength + $bLength); + } + + /** + * Helper function for calculating the ratio to measure similarity for the strings. + * The ratio is defined as being 2 * (number of matches / total length) + * + * @param int $matches The number of matches in the two strings. + * @param int $length The length of the two strings. + * @return float The calculated ratio. + */ + private function calculateRatio($matches, $length=0) + { + if($length) { + return 2 * ($matches / $length); + } + else { + return 1; + } + } + + /** + * Helper function that provides the ability to return the value for a key + * in an array of it exists, or if it doesn't then return a default value. + * Essentially cleaner than doing a series of if(isset()) {} else {} calls. + * + * @param array $array The array to search. + * @param string $key The key to check that exists. + * @param mixed $default The value to return as the default value if the key doesn't exist. + * @return mixed The value from the array if the key exists or otherwise the default. + */ + private function arrayGetDefault($array, $key, $default) + { + if(isset($array[$key])) { + return $array[$key]; + } + else { + return $default; + } + } + + /** + * Sort an array by the nested arrays it contains. Helper function for getMatchingBlocks + * + * @param array $a First array to compare. + * @param array $b Second array to compare. + * @return int -1, 0 or 1, as expected by the usort function. + */ + private function tupleSort($a, $b) + { + $max = max(count($a), count($b)); + for($i = 0; $i < $max; ++$i) { + if($a[$i] < $b[$i]) { + return -1; + } + else if($a[$i] > $b[$i]) { + return 1; + } + } + + if(count($a) == $count($b)) { + return 0; + } + else if(count($a) < count($b)) { + return -1; + } + else { + return 1; + } + } +} -- cgit v1.2.3