summaryrefslogtreecommitdiffstats
path: root/vendor/gipfl/web/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:23:16 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:23:16 +0000
commit3e97c51418e6d27e9a81906f347fcb7c78e27d4f (patch)
treeee596ce1bc9840661386f96f9b8d1f919a106317 /vendor/gipfl/web/src
parentInitial commit. (diff)
downloadicingaweb2-module-incubator-3e97c51418e6d27e9a81906f347fcb7c78e27d4f.tar.xz
icingaweb2-module-incubator-3e97c51418e6d27e9a81906f347fcb7c78e27d4f.zip
Adding upstream version 0.20.0.upstream/0.20.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gipfl/web/src')
-rw-r--r--vendor/gipfl/web/src/Form.php281
-rw-r--r--vendor/gipfl/web/src/Form/Decorator/DdDtDecorator.php158
-rw-r--r--vendor/gipfl/web/src/Form/Element/Boolean.php39
-rw-r--r--vendor/gipfl/web/src/Form/Element/MultiSelect.php119
-rw-r--r--vendor/gipfl/web/src/Form/Element/Password.php11
-rw-r--r--vendor/gipfl/web/src/Form/Element/TextWithActionButton.php104
-rw-r--r--vendor/gipfl/web/src/Form/Feature/NextConfirmCancel.php153
-rw-r--r--vendor/gipfl/web/src/Form/Validator/AlwaysFailValidator.php16
-rw-r--r--vendor/gipfl/web/src/Form/Validator/PhpSessionBasedCsrfTokenValidator.php34
-rw-r--r--vendor/gipfl/web/src/Form/Validator/SimpleValidator.php27
-rw-r--r--vendor/gipfl/web/src/HtmlHelper.php29
-rw-r--r--vendor/gipfl/web/src/InlineForm.php10
-rw-r--r--vendor/gipfl/web/src/Table/NameValueTable.php47
-rw-r--r--vendor/gipfl/web/src/Widget/CollapsibleList.php74
-rw-r--r--vendor/gipfl/web/src/Widget/ConfigDiff.php106
-rw-r--r--vendor/gipfl/web/src/Widget/Hint.php45
-rw-r--r--vendor/gipfl/web/src/vendor/php-diff/lib/Diff.php179
-rw-r--r--vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Abstract.php82
-rw-r--r--vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Array.php230
-rw-r--r--vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Inline.php143
-rw-r--r--vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/SideBySide.php163
-rw-r--r--vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Context.php128
-rw-r--r--vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Unified.php87
-rw-r--r--vendor/gipfl/web/src/vendor/php-diff/lib/Diff/SequenceMatcher.php742
24 files changed, 3007 insertions, 0 deletions
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 @@
+<?php
+
+namespace gipfl\Web;
+
+use Exception;
+use gipfl\Web\Form\Decorator\DdDtDecorator;
+use gipfl\Web\Form\Validator\AlwaysFailValidator;
+use gipfl\Web\Form\Validator\PhpSessionBasedCsrfTokenValidator;
+use gipfl\Web\Widget\Hint;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Contract\FormElement;
+use ipl\Html\Error;
+use ipl\Html\Form as iplForm;
+use ipl\Html\FormElement\BaseFormElement;
+use ipl\Html\FormElement\HiddenElement;
+use ipl\Html\Html;
+use RuntimeException;
+use function array_key_exists;
+use function get_class;
+use function parse_str;
+
+class Form extends iplForm
+{
+ protected $formNameElementName = '__FORM_NAME';
+
+ protected $useCsrf = true;
+
+ protected $useFormName = true;
+
+ protected $defaultDecoratorClass = DdDtDecorator::class;
+
+ protected $formCssClasses = ['gipfl-form'];
+
+ /** @var boolean|null */
+ protected $hasBeenSubmitted;
+
+ public function ensureAssembled()
+ {
+ if ($this->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 @@
+<?php
+
+namespace gipfl\Web\Form\Decorator;
+
+use gipfl\Web\HtmlHelper;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\FormDecorator\DecoratorInterface;
+use ipl\Html\FormElement\BaseFormElement;
+use ipl\Html\Html;
+use ipl\Html\HtmlDocument;
+
+class DdDtDecorator extends BaseHtmlElement implements DecoratorInterface
+{
+ const CSS_CLASS_ELEMENT_HAS_ERRORS = 'gipfl-form-element-has-errors';
+
+ const CSS_CLASS_ELEMENT_ERRORS = 'gipfl-form-element-errors';
+
+ const CSS_CLASS_DESCRIPTION = 'gipfl-element-description';
+
+ protected $tag = 'dl';
+
+ protected $dt;
+
+ protected $dd;
+
+ /** @var BaseFormElement */
+ protected $element;
+
+ /** @var HtmlDocument */
+ protected $elementDoc;
+
+ /**
+ * @param BaseFormElement $element
+ * @return static
+ */
+ public function decorate(BaseFormElement $element)
+ {
+ $decorator = clone($this);
+ $decorator->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 @@
+<?php
+
+namespace gipfl\Web\Form\Element;
+
+use gipfl\Translation\TranslationHelper;
+use ipl\Html\FormElement\SelectElement;
+
+class Boolean extends SelectElement
+{
+ use TranslationHelper;
+
+ public function __construct($name, $attributes = null)
+ {
+ parent::__construct($name, $attributes);
+ $options = [
+ 'y' => $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 @@
+<?php
+
+namespace gipfl\Web\Form\Element;
+
+use ipl\Html\Attributes;
+use ipl\Html\FormElement\SelectElement;
+
+class MultiSelect extends SelectElement
+{
+ protected $value = [];
+
+ public function __construct($name, $attributes = null)
+ {
+ // Make sure we set value last as it depends on options
+ if (isset($attributes['value'])) {
+ $value = $attributes['value'];
+ unset($attributes['value']);
+ $attributes['value'] = $value;
+ }
+
+ parent::__construct($name, $attributes);
+
+ $this->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 @@
+<?php
+
+namespace gipfl\Web\Form\Element;
+
+use ipl\Html\FormElement\TextElement;
+
+class Password extends TextElement
+{
+ // TODO
+ protected $type = 'password';
+}
diff --git a/vendor/gipfl/web/src/Form/Element/TextWithActionButton.php b/vendor/gipfl/web/src/Form/Element/TextWithActionButton.php
new file mode 100644
index 0000000..13ebfb8
--- /dev/null
+++ b/vendor/gipfl/web/src/Form/Element/TextWithActionButton.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace gipfl\Web\Form\Element;
+
+use gipfl\Web\Form\Decorator\DdDtDecorator;
+use ipl\Html\Attributes;
+use ipl\Html\Form;
+use ipl\Html\FormElement\SubmitElement;
+use ipl\Html\FormElement\TextElement;
+
+class TextWithActionButton
+{
+ /** @var SubmitElement */
+ protected $button;
+
+ /** @var TextElement */
+ protected $element;
+
+ protected $buttonSuffix = '_related_button';
+
+ /** @var string */
+ protected $elementName;
+
+ /** @var array|Attributes */
+ protected $elementAttributes;
+
+ /** @var array|Attributes */
+ protected $buttonAttributes;
+
+ protected $elementClasses = ['input-with-button'];
+
+ protected $buttonClasses = ['input-element-related-button'];
+
+ /**
+ * TextWithActionButton constructor.
+ * @param string $elementName
+ * @param array|Attributes $elementAttributes
+ * @param array|Attributes $buttonAttributes
+ */
+ public function __construct($elementName, $elementAttributes, $buttonAttributes)
+ {
+ $this->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 @@
+<?php
+
+namespace gipfl\Web\Form\Feature;
+
+use gipfl\Web\Form;
+use ipl\Html\DeferredText;
+use ipl\Html\FormElement\BaseFormElement;
+use ipl\Html\FormElement\SubmitElement;
+use ipl\Html\HtmlDocument;
+use ipl\Html\ValidHtml;
+
+class NextConfirmCancel
+{
+ /** @var SubmitElement */
+ protected $next;
+
+ /** @var SubmitElement */
+ protected $confirm;
+
+ /** @var SubmitElement */
+ protected $cancel;
+
+ protected $withNext;
+
+ protected $withNextContent;
+
+ protected $withConfirm;
+
+ protected $withConfirmContent;
+
+ protected $confirmFirst = true;
+
+ public function __construct(SubmitElement $next, SubmitElement $confirm, SubmitElement $cancel)
+ {
+ $this->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 @@
+<?php
+
+namespace gipfl\Web\Form\Validator;
+
+class AlwaysFailValidator extends SimpleValidator
+{
+ public function isValid($value)
+ {
+ $message = $this->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 @@
+<?php
+
+namespace gipfl\Web\Form\Validator;
+
+class PhpSessionBasedCsrfTokenValidator extends SimpleValidator
+{
+ public function isValid($value)
+ {
+ if (strpos($value, '|') === false) {
+ return false;
+ }
+
+ list($seed, $token) = \explode('|', $value, 2);
+
+ if (! \is_numeric($seed)) {
+ return false;
+ }
+
+ if ($token === \hash('sha256', \session_id() . $seed)) {
+ return true;
+ } else {
+ $this->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 @@
+<?php
+
+namespace gipfl\Web\Form\Validator;
+
+use ipl\Stdlib\Contract\Validator;
+use ipl\Stdlib\Messages;
+
+abstract class SimpleValidator implements Validator
+{
+ use Messages;
+
+ protected $settings = [];
+
+ public function __construct(array $settings = [])
+ {
+ $this->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 @@
+<?php
+
+namespace gipfl\Web;
+
+use ipl\Html\Attributes;
+use ipl\Html\BaseHtmlElement;
+
+abstract class HtmlHelper
+{
+ public static function elementHasClass(BaseHtmlElement $element, $class)
+ {
+ return static::classIsSet($element->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 @@
+<?php
+
+namespace gipfl\Web;
+
+class InlineForm extends Form
+{
+ protected $defaultDecoratorClass = null;
+
+ protected $formCssClasses = ['gipfl-form', 'gipfl-inline-form'];
+}
diff --git a/vendor/gipfl/web/src/Table/NameValueTable.php b/vendor/gipfl/web/src/Table/NameValueTable.php
new file mode 100644
index 0000000..7227de7
--- /dev/null
+++ b/vendor/gipfl/web/src/Table/NameValueTable.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace gipfl\Web\Table;
+
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Table;
+
+class NameValueTable extends Table
+{
+ protected $defaultAttributes = ['class' => '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 @@
+<?php
+
+namespace gipfl\Web\Widget;
+
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+use InvalidArgumentException;
+use LogicException;
+use function count;
+
+class CollapsibleList extends BaseHtmlElement
+{
+ protected $tag = 'ul';
+
+ protected $defaultAttributes = [
+ 'class' => '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 @@
+<?php
+
+namespace gipfl\Web\Widget;
+
+use Diff;
+use ipl\Html\ValidHtml;
+use InvalidArgumentException;
+
+/**
+ * @deprecated - please use gipfl\Diff
+ */
+class ConfigDiff implements ValidHtml
+{
+ protected $a;
+
+ protected $b;
+
+ protected $diff;
+
+ protected $htmlRenderer = 'SideBySide';
+
+ protected $knownHtmlRenderers = [
+ 'SideBySide',
+ 'Inline',
+ ];
+
+ protected $knownTextRenderers = [
+ 'Context',
+ 'Unified',
+ ];
+
+ protected $vendorDir;
+
+ protected function __construct($a, $b)
+ {
+ $this->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 @@
+<?php
+
+namespace gipfl\Web\Widget;
+
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+
+class Hint extends BaseHtmlElement
+{
+ protected $tag = 'div';
+
+ protected $defaultAttributes = [
+ 'class' => '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 @@
+<?php
+/**
+ * Diff
+ *
+ * A comprehensive library for generating differences between two strings
+ * in multiple formats (unified, side by side HTML etc)
+ *
+ * PHP version 5
+ *
+ * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
+ *
+ * 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 <chris.boulton@interspire.com>
+ * @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 @@
+<?php
+/**
+ * Abstract class for diff renderers in PHP DiffLib.
+ *
+ * PHP version 5
+ *
+ * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
+ *
+ * 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 <chris.boulton@interspire.com>
+ * @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 @@
+<?php
+/**
+ * Base renderer for rendering HTML based diffs for PHP DiffLib.
+ *
+ * PHP version 5
+ *
+ * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
+ *
+ * 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 <chris.boulton@interspire.com>
+ * @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('<del>', '</del>'), $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('<ins>', '</ins>'), $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 &nbsp; 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 &nbsp;.
+ *
+ * @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('&nbsp; ', $div).str_repeat('&nbsp;', $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 @@
+<?php
+/**
+ * Inline HTML diff generator for PHP DiffLib.
+ *
+ * PHP version 5
+ *
+ * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
+ *
+ * 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 <chris.boulton@interspire.com>
+ * @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 .= '<table class="Differences DifferencesInline">';
+ $html .= '<thead>';
+ $html .= '<tr>';
+ $html .= '<th>Old</th>';
+ $html .= '<th>New</th>';
+ $html .= '<th>Differences</th>';
+ $html .= '</tr>';
+ $html .= '</thead>';
+ 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 .= '<tbody class="Skipped">';
+ $html .= '<th>&hellip;</th>';
+ $html .= '<th>&hellip;</th>';
+ $html .= '<td>&nbsp;</td>';
+ $html .= '</tbody>';
+ }
+
+ foreach($blocks as $change) {
+ $html .= '<tbody class="Change'.ucfirst($change['tag']).'">';
+ // 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 .= '<tr>';
+ $html .= '<th>'.$fromLine.'</th>';
+ $html .= '<th>'.$toLine.'</th>';
+ $html .= '<td class="Left">'.$line.'</td>';
+ $html .= '</tr>';
+ }
+ }
+ // 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 .= '<tr>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<th>'.$toLine.'</th>';
+ $html .= '<td class="Right"><ins>'.$line.'</ins>&nbsp;</td>';
+ $html .= '</tr>';
+ }
+ }
+ // 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 .= '<tr>';
+ $html .= '<th>'.$fromLine.'</th>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<td class="Left"><del>'.$line.'</del>&nbsp;</td>';
+ $html .= '</tr>';
+ }
+ }
+ // 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 .= '<tr>';
+ $html .= '<th>'.$fromLine.'</th>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<td class="Left"><span>'.$line.'</span></td>';
+ $html .= '</tr>';
+ }
+
+ foreach($change['changed']['lines'] as $no => $line) {
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<th>'.$toLine.'</th>';
+ $html .= '<td class="Right"><span>'.$line.'</span></td>';
+ $html .= '</tr>';
+ }
+ }
+ $html .= '</tbody>';
+ }
+ }
+ $html .= '</table>';
+ 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 @@
+<?php
+/**
+ * Side by Side HTML diff generator for PHP DiffLib.
+ *
+ * PHP version 5
+ *
+ * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
+ *
+ * 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 <chris.boulton@interspire.com>
+ * @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 .= '<table class="Differences DifferencesSideBySide">';
+ $html .= '<thead>';
+ $html .= '<tr>';
+ $html .= '<th colspan="2">Old Version</th>';
+ $html .= '<th colspan="2">New Version</th>';
+ $html .= '</tr>';
+ $html .= '</thead>';
+ foreach($changes as $i => $blocks) {
+ if($i > 0) {
+ $html .= '<tbody class="Skipped">';
+ $html .= '<th>&hellip;</th><td>&nbsp;</td>';
+ $html .= '<th>&hellip;</th><td>&nbsp;</td>';
+ $html .= '</tbody>';
+ }
+
+ foreach($blocks as $change) {
+ $html .= '<tbody class="Change'.ucfirst($change['tag']).'">';
+ // 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 .= '<tr>';
+ $html .= '<th>'.$fromLine.'</th>';
+ $html .= '<td class="Left"><span>'.$line.'</span>&nbsp;</span></td>';
+ $html .= '<th>'.$toLine.'</th>';
+ $html .= '<td class="Right"><span>'.$line.'</span>&nbsp;</span></td>';
+ $html .= '</tr>';
+ }
+ }
+ // 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 .= '<tr>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<td class="Left">&nbsp;</td>';
+ $html .= '<th>'.$toLine.'</th>';
+ $html .= '<td class="Right"><ins>'.$line.'</ins>&nbsp;</td>';
+ $html .= '</tr>';
+ }
+ }
+ // 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 .= '<tr>';
+ $html .= '<th>'.$fromLine.'</th>';
+ $html .= '<td class="Left"><del>'.$line.'</del>&nbsp;</td>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<td class="Right">&nbsp;</td>';
+ $html .= '</tr>';
+ }
+ }
+ // 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 .= '<tr>';
+ $html .= '<th>'.$fromLine.'</th>';
+ $html .= '<td class="Left"><span>'.$line.'</span>&nbsp;</td>';
+ if(!isset($change['changed']['lines'][$no])) {
+ $toLine = '&nbsp;';
+ $changedLine = '&nbsp;';
+ }
+ else {
+ $toLine = $change['base']['offset'] + $no + 1;
+ $changedLine = '<span>'.$change['changed']['lines'][$no].'</span>';
+ }
+ $html .= '<th>'.$toLine.'</th>';
+ $html .= '<td class="Right">'.$changedLine.'</td>';
+ $html .= '</tr>';
+ }
+ }
+ else {
+ foreach($change['changed']['lines'] as $no => $changedLine) {
+ if(!isset($change['base']['lines'][$no])) {
+ $fromLine = '&nbsp;';
+ $line = '&nbsp;';
+ }
+ else {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $line = '<span>'.$change['base']['lines'][$no].'</span>';
+ }
+ $html .= '<tr>';
+ $html .= '<th>'.$fromLine.'</th>';
+ $html .= '<td class="Left"><span>'.$line.'</span>&nbsp;</td>';
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '<th>'.$toLine.'</th>';
+ $html .= '<td class="Right">'.$changedLine.'</td>';
+ $html .= '</tr>';
+ }
+ }
+ }
+ $html .= '</tbody>';
+ }
+ }
+ $html .= '</table>';
+ 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 @@
+<?php
+/**
+ * Context diff generator for PHP DiffLib.
+ *
+ * PHP version 5
+ *
+ * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
+ *
+ * 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 <chris.boulton@interspire.com>
+ * @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 @@
+<?php
+/**
+ * Unified diff generator for PHP DiffLib.
+ *
+ * PHP version 5
+ *
+ * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
+ *
+ * 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 <chris.boulton@interspire.com>
+ * @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 @@
+<?php
+/**
+ * Sequence matcher for Diff
+ *
+ * PHP version 5
+ *
+ * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
+ *
+ * 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 <chris.boulton@interspire.com>
+ * @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;
+ }
+ }
+}