summaryrefslogtreecommitdiffstats
path: root/vendor/ipl/web/src/Common
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/ipl/web/src/Common')
-rw-r--r--vendor/ipl/web/src/Common/BaseItemList.php73
-rw-r--r--vendor/ipl/web/src/Common/BaseItemTable.php88
-rw-r--r--vendor/ipl/web/src/Common/BaseListItem.php145
-rw-r--r--vendor/ipl/web/src/Common/BaseOrderedItemList.php31
-rw-r--r--vendor/ipl/web/src/Common/BaseOrderedListItem.php42
-rw-r--r--vendor/ipl/web/src/Common/BaseTableRowItem.php119
-rw-r--r--vendor/ipl/web/src/Common/BaseTarget.php36
-rw-r--r--vendor/ipl/web/src/Common/Card.php59
-rw-r--r--vendor/ipl/web/src/Common/CsrfCounterMeasure.php48
-rw-r--r--vendor/ipl/web/src/Common/FormUid.php59
-rw-r--r--vendor/ipl/web/src/Common/RedirectOption.php41
-rw-r--r--vendor/ipl/web/src/Common/StateBadges.php194
12 files changed, 935 insertions, 0 deletions
diff --git a/vendor/ipl/web/src/Common/BaseItemList.php b/vendor/ipl/web/src/Common/BaseItemList.php
new file mode 100644
index 0000000..ce0946c
--- /dev/null
+++ b/vendor/ipl/web/src/Common/BaseItemList.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace ipl\Web\Common;
+
+use InvalidArgumentException;
+use ipl\Html\BaseHtmlElement;
+use ipl\Orm\ResultSet;
+use ipl\Stdlib\BaseFilter;
+use ipl\Web\Widget\EmptyStateBar;
+
+/**
+ * Base class for item lists
+ */
+abstract class BaseItemList extends BaseHtmlElement
+{
+ use BaseFilter;
+
+ /** @var array<string, mixed> */
+ protected $baseAttributes = [
+ 'class' => ['item-list', 'default-layout'],
+ 'data-base-target' => '_next',
+ 'data-pdfexport-page-breaks-at' => '.list-item'
+ ];
+
+ /** @var ResultSet|iterable<object> */
+ protected $data;
+
+ protected $tag = 'ul';
+
+ /**
+ * Create a new item list
+ *
+ * @param ResultSet|iterable<object> $data Data source of the list
+ */
+ public function __construct($data)
+ {
+ if (! is_iterable($data)) {
+ throw new InvalidArgumentException('Data must be an array or an instance of Traversable');
+ }
+
+ $this->data = $data;
+
+ $this->addAttributes($this->baseAttributes);
+
+ $this->init();
+ }
+
+ abstract protected function getItemClass(): string;
+
+ /**
+ * Initialize the item list
+ *
+ * If you want to adjust the item list after construction, override this method.
+ */
+ protected function init(): void
+ {
+ }
+
+ protected function assemble(): void
+ {
+ $itemClass = $this->getItemClass();
+ foreach ($this->data as $data) {
+ /** @var BaseListItem|BaseTableRowItem $item */
+ $item = new $itemClass($data, $this);
+ $this->addHtml($item);
+ }
+
+ if ($this->isEmpty()) {
+ $this->setTag('div');
+ $this->addHtml(new EmptyStateBar(t('No items found.')));
+ }
+ }
+}
diff --git a/vendor/ipl/web/src/Common/BaseItemTable.php b/vendor/ipl/web/src/Common/BaseItemTable.php
new file mode 100644
index 0000000..f6ca212
--- /dev/null
+++ b/vendor/ipl/web/src/Common/BaseItemTable.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace ipl\Web\Common;
+
+use InvalidArgumentException;
+use ipl\Html\BaseHtmlElement;
+use ipl\Orm\ResultSet;
+use ipl\Stdlib\BaseFilter;
+use ipl\Web\Widget\EmptyStateBar;
+
+/**
+ * Base class for item tables
+ */
+abstract class BaseItemTable extends BaseHtmlElement
+{
+ use BaseFilter;
+
+ /** @var string Defines the layout used by this item */
+ public const TABLE_LAYOUT = 'table-layout';
+
+ /** @var array<string, mixed> */
+ protected $baseAttributes = [
+ 'class' => 'item-table',
+ 'data-base-target' => '_next'
+ ];
+
+ /** @var ResultSet|iterable<object> */
+ protected $data;
+
+ protected $tag = 'ul';
+
+ /**
+ * Create a new item table
+ *
+ * @param ResultSet|iterable<object> $data Data source of the table
+ */
+ public function __construct($data)
+ {
+ if (! is_iterable($data)) {
+ throw new InvalidArgumentException('Data must be an array or an instance of Traversable');
+ }
+
+ $this->data = $data;
+
+ $this->addAttributes($this->baseAttributes);
+
+ $this->init();
+ }
+
+ /**
+ * Initialize the item table
+ *
+ * If you want to adjust the item table after construction, override this method.
+ */
+ protected function init(): void
+ {
+ }
+
+ /**
+ * Get the table layout to use
+ *
+ * @return string
+ */
+ protected function getLayout(): string
+ {
+ return static::TABLE_LAYOUT;
+ }
+
+ abstract protected function getItemClass(): string;
+
+ protected function assemble(): void
+ {
+ $this->addAttributes(['class' => $this->getLayout()]);
+
+ $itemClass = $this->getItemClass();
+ foreach ($this->data as $data) {
+ /** @var BaseTableRowItem $item */
+ $item = new $itemClass($data, $this);
+
+ $this->addHtml($item);
+ }
+
+ if ($this->isEmpty()) {
+ $this->setTag('div');
+ $this->addHtml(new EmptyStateBar(t('No items found.')));
+ }
+ }
+}
diff --git a/vendor/ipl/web/src/Common/BaseListItem.php b/vendor/ipl/web/src/Common/BaseListItem.php
new file mode 100644
index 0000000..cf143ee
--- /dev/null
+++ b/vendor/ipl/web/src/Common/BaseListItem.php
@@ -0,0 +1,145 @@
+<?php
+
+namespace ipl\Web\Common;
+
+use ipl\Html\Attributes;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\HtmlElement;
+
+/**
+ * Base class for list items
+ */
+abstract class BaseListItem extends BaseHtmlElement
+{
+ /** @var array<string, mixed> */
+ protected $baseAttributes = ['class' => 'list-item'];
+
+ /** @var object The associated list item */
+ protected $item;
+
+ /** @var BaseItemList The list where the item is part of */
+ protected $list;
+
+ protected $tag = 'li';
+
+ /**
+ * Create a new list item
+ *
+ * @param object $item
+ * @param BaseItemList $list
+ */
+ public function __construct($item, BaseItemList $list)
+ {
+ $this->item = $item;
+ $this->list = $list;
+
+ $this->addAttributes($this->baseAttributes);
+
+ $this->init();
+ }
+
+ abstract protected function assembleHeader(BaseHtmlElement $header): void;
+
+ abstract protected function assembleMain(BaseHtmlElement $main): void;
+
+ protected function assembleFooter(BaseHtmlElement $footer): void
+ {
+ }
+
+ protected function assembleCaption(BaseHtmlElement $caption): void
+ {
+ }
+
+ protected function assembleTitle(BaseHtmlElement $title): void
+ {
+ }
+
+ protected function assembleVisual(BaseHtmlElement $visual): void
+ {
+ }
+
+ protected function createCaption(): BaseHtmlElement
+ {
+ $caption = new HtmlElement('section', Attributes::create(['class' => 'caption']));
+
+ $this->assembleCaption($caption);
+
+ return $caption;
+ }
+
+ protected function createHeader(): BaseHtmlElement
+ {
+ $header = new HtmlElement('header');
+
+ $this->assembleHeader($header);
+
+ return $header;
+ }
+
+ protected function createMain(): BaseHtmlElement
+ {
+ $main = new HtmlElement('div', Attributes::create(['class' => 'main']));
+
+ $this->assembleMain($main);
+
+ return $main;
+ }
+
+ protected function createFooter(): ?BaseHtmlElement
+ {
+ $footer = new HtmlElement('footer');
+
+ $this->assembleFooter($footer);
+ if ($footer->isEmpty()) {
+ return null;
+ }
+
+ return $footer;
+ }
+
+ protected function createTimestamp(): ?BaseHtmlElement
+ {
+ return null;
+ }
+
+ protected function createTitle(): BaseHtmlElement
+ {
+ $title = new HtmlElement('div', Attributes::create(['class' => 'title']));
+
+ $this->assembleTitle($title);
+
+ return $title;
+ }
+
+ /**
+ * @return ?BaseHtmlElement
+ */
+ protected function createVisual(): ?BaseHtmlElement
+ {
+ $visual = new HtmlElement('div', Attributes::create(['class' => 'visual']));
+
+ $this->assembleVisual($visual);
+ if ($visual->isEmpty()) {
+ return null;
+ }
+
+ return $visual;
+ }
+
+ /**
+ * Initialize the list item
+ *
+ * If you want to adjust the list item after construction, override this method.
+ */
+ protected function init(): void
+ {
+ }
+
+ protected function assemble(): void
+ {
+ $this->add([
+ $this->createVisual(),
+ $this->createMain()
+ ]);
+ }
+}
diff --git a/vendor/ipl/web/src/Common/BaseOrderedItemList.php b/vendor/ipl/web/src/Common/BaseOrderedItemList.php
new file mode 100644
index 0000000..c141fc5
--- /dev/null
+++ b/vendor/ipl/web/src/Common/BaseOrderedItemList.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace ipl\Web\Common;
+
+use ipl\Web\Widget\EmptyStateBar;
+
+/**
+ * @method BaseOrderedListItem getItemClass()
+ */
+abstract class BaseOrderedItemList extends BaseItemList
+{
+ protected $tag = 'ol';
+
+ protected function assemble(): void
+ {
+ $itemClass = $this->getItemClass();
+
+ $i = 0;
+ foreach ($this->data as $data) {
+ $item = new $itemClass($data, $this);
+ $item->setOrder($i++);
+
+ $this->addHtml($item);
+ }
+
+ if ($this->isEmpty()) {
+ $this->setTag('div');
+ $this->addHtml(new EmptyStateBar(t('No items found.')));
+ }
+ }
+}
diff --git a/vendor/ipl/web/src/Common/BaseOrderedListItem.php b/vendor/ipl/web/src/Common/BaseOrderedListItem.php
new file mode 100644
index 0000000..03b387d
--- /dev/null
+++ b/vendor/ipl/web/src/Common/BaseOrderedListItem.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace ipl\Web\Common;
+
+use LogicException;
+
+abstract class BaseOrderedListItem extends BaseListItem
+{
+ /** @var ?int This element's position */
+ protected $order;
+
+ /**
+ * Set this element's position
+ *
+ * @param int $order
+ *
+ * @return $this
+ */
+ public function setOrder(int $order): self
+ {
+ $this->order = $order;
+
+ return $this;
+ }
+
+ /**
+ * Get this element's position
+ *
+ * @return int
+ * @throws LogicException When calling this method without setting the `order` property
+ */
+ public function getOrder(): int
+ {
+ if ($this->order === null) {
+ throw new LogicException(
+ 'You are accessing an unset property. Please make sure to set it beforehand.'
+ );
+ }
+
+ return $this->order;
+ }
+}
diff --git a/vendor/ipl/web/src/Common/BaseTableRowItem.php b/vendor/ipl/web/src/Common/BaseTableRowItem.php
new file mode 100644
index 0000000..bc61c8e
--- /dev/null
+++ b/vendor/ipl/web/src/Common/BaseTableRowItem.php
@@ -0,0 +1,119 @@
+<?php
+
+namespace ipl\Web\Common;
+
+use ipl\Html\Attributes;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+use ipl\Html\HtmlDocument;
+use ipl\Html\HtmlElement;
+
+abstract class BaseTableRowItem extends BaseHtmlElement
+{
+ /** @var array<string, mixed> */
+ protected $baseAttributes = ['class' => 'table-row'];
+
+ /** @var object The associated list item */
+ protected $item;
+
+ /** @var ?BaseItemTable The list where the item is part of */
+ protected $table;
+
+ protected $tag = 'li';
+
+ /**
+ * Create a new table row item
+ *
+ * @param object $item
+ * @param BaseItemTable|null $table
+ */
+ public function __construct($item, BaseItemTable $table = null)
+ {
+ $this->item = $item;
+ $this->table = $table;
+
+ if ($table === null) {
+ $this->setTag('div');
+ }
+
+ $this->addAttributes($this->baseAttributes);
+
+ $this->init();
+ }
+
+ abstract protected function assembleTitle(BaseHtmlElement $title): void;
+
+ protected function assembleColumns(HtmlDocument $columns): void
+ {
+ }
+
+ protected function assembleVisual(BaseHtmlElement $visual): void
+ {
+ }
+
+ /**
+ * Create column
+ *
+ * @param mixed $content
+ *
+ * @return BaseHtmlElement
+ */
+ protected function createColumn($content = null): BaseHtmlElement
+ {
+ return new HtmlElement(
+ 'div',
+ Attributes::create(['class' => 'col']),
+ new HtmlElement(
+ 'div',
+ Attributes::create(['class' => 'content']),
+ ...Html::wantHtmlList($content)
+ )
+ );
+ }
+
+ protected function createColumns(): HtmlDocument
+ {
+ $columns = new HtmlDocument();
+
+ $this->assembleColumns($columns);
+
+ return $columns;
+ }
+
+ protected function createTitle(): BaseHtmlElement
+ {
+ $title = $this->createColumn()->addAttributes(['class' => 'title']);
+
+ $this->assembleTitle($title->getFirst('div'));
+
+ $title->prepend($this->createVisual());
+
+ return $title;
+ }
+
+ protected function createVisual(): ?BaseHtmlElement
+ {
+ $visual = new HtmlElement('div', Attributes::create(['class' => 'visual']));
+
+ $this->assembleVisual($visual);
+
+ return $visual->isEmpty() ? null : $visual;
+ }
+
+ /**
+ * Initialize the list item
+ *
+ * If you want to adjust the list item after construction, override this method.
+ */
+ protected function init(): void
+ {
+ }
+
+ protected function assemble(): void
+ {
+ $this->addHtml(
+ $this->createTitle(),
+ $this->createColumns()
+ );
+ }
+}
diff --git a/vendor/ipl/web/src/Common/BaseTarget.php b/vendor/ipl/web/src/Common/BaseTarget.php
new file mode 100644
index 0000000..080f6c6
--- /dev/null
+++ b/vendor/ipl/web/src/Common/BaseTarget.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace ipl\Web\Common;
+
+/**
+ * @method \ipl\Html\Attributes getAttributes()
+ */
+trait BaseTarget
+{
+ /**
+ * Get the data-base-target attribute
+ *
+ * @return string|null
+ */
+ public function getBaseTarget(): ?string
+ {
+ /** @var ?string $baseTarget */
+ $baseTarget = $this->getAttributes()->get('data-base-target')->getValue();
+
+ return $baseTarget;
+ }
+
+ /**
+ * Set the data-base-target attribute
+ *
+ * @param string $target
+ *
+ * @return $this
+ */
+ public function setBaseTarget(string $target): self
+ {
+ $this->getAttributes()->set('data-base-target', $target);
+
+ return $this;
+ }
+}
diff --git a/vendor/ipl/web/src/Common/Card.php b/vendor/ipl/web/src/Common/Card.php
new file mode 100644
index 0000000..434132c
--- /dev/null
+++ b/vendor/ipl/web/src/Common/Card.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace ipl\Web\Common;
+
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+
+abstract class Card extends BaseHtmlElement
+{
+ protected $tag = 'section';
+
+ abstract protected function assembleBody(BaseHtmlElement $body);
+
+ abstract protected function assembleHeader(BaseHtmlElement $header);
+
+ protected function assembleFooter(BaseHtmlElement $footer)
+ {
+ }
+
+ protected function createBody()
+ {
+ $body = Html::tag('div', ['class' => 'card-body']);
+
+ $this->assembleBody($body);
+
+ return $body;
+ }
+
+ protected function createFooter()
+ {
+ $footer = Html::tag('div', ['class' => 'card-footer']);
+
+ $this->assembleFooter($footer);
+
+ if (! $footer->isEmpty()) {
+ return $footer;
+ }
+ }
+
+ protected function createHeader()
+ {
+ $header = Html::tag('div', ['class' => 'card-header']);
+
+ $this->assembleHeader($header);
+
+ return $header;
+ }
+
+ protected function assemble()
+ {
+ $this->addAttributes(['class' => 'card']);
+
+ $this->add([
+ $this->createHeader(),
+ $this->createBody(),
+ $this->createFooter()
+ ]);
+ }
+}
diff --git a/vendor/ipl/web/src/Common/CsrfCounterMeasure.php b/vendor/ipl/web/src/Common/CsrfCounterMeasure.php
new file mode 100644
index 0000000..348c4ee
--- /dev/null
+++ b/vendor/ipl/web/src/Common/CsrfCounterMeasure.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace ipl\Web\Common;
+
+use ipl\Html\Contract\FormElement;
+use ipl\Html\Form;
+
+trait CsrfCounterMeasure
+{
+ /**
+ * Create a form element to counter measure CSRF attacks
+ *
+ * @param string $uniqueId A unique ID that persists through different requests
+ *
+ * @return FormElement
+ */
+ protected function createCsrfCounterMeasure($uniqueId)
+ {
+ $hashAlgo = in_array('sha3-256', hash_algos(), true) ? 'sha3-256' : 'sha256';
+
+ $seed = random_bytes(16);
+ $token = base64_encode($seed) . '|' . hash($hashAlgo, $uniqueId . $seed);
+
+ /** @var Form $this */
+ return $this->createElement(
+ 'hidden',
+ 'CSRFToken',
+ [
+ 'ignore' => true,
+ 'required' => true,
+ 'value' => $token,
+ 'validators' => ['Callback' => function ($token) use ($uniqueId, $hashAlgo) {
+ if (strpos($token, '|') === false) {
+ die('Invalid CSRF token provided');
+ }
+
+ list($seed, $hash) = explode('|', $token);
+
+ if ($hash !== hash($hashAlgo, $uniqueId . base64_decode($seed))) {
+ die('Invalid CSRF token provided');
+ }
+
+ return true;
+ }]
+ ]
+ );
+ }
+}
diff --git a/vendor/ipl/web/src/Common/FormUid.php b/vendor/ipl/web/src/Common/FormUid.php
new file mode 100644
index 0000000..05aac7b
--- /dev/null
+++ b/vendor/ipl/web/src/Common/FormUid.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace ipl\Web\Common;
+
+use ipl\Html\Form;
+use ipl\Html\Contract\FormElement;
+use LogicException;
+
+trait FormUid
+{
+ protected $uidElementName = 'uid';
+
+ /**
+ * Create a form element to make this form distinguishable from others
+ *
+ * You'll have to define a name for the form for this to work.
+ *
+ * @return FormElement
+ */
+ protected function createUidElement()
+ {
+ /** @var Form $this */
+ $element = $this->createElement('hidden', $this->uidElementName, ['ignore' => true]);
+ $element->getAttributes()->registerAttributeCallback('value', function () {
+ /** @var Form $this */
+ return $this->getAttributes()->get('name')->getValue();
+ });
+
+ return $element;
+ }
+
+ /**
+ * Get whether the form has been sent
+ *
+ * A form is considered sent if the request's method equals the form's method
+ * and the sent UID is the form's UID.
+ *
+ * @return bool
+ */
+ public function hasBeenSent()
+ {
+ if (! parent::hasBeenSent()) {
+ return false;
+ } elseif ($this->getMethod() === 'GET') {
+ // Get forms are unlikely to require a UID. If they do, change this.
+ return true;
+ }
+
+ /** @var Form $this */
+ $name = $this->getAttributes()->get('name')->getValue();
+ if (! $name) {
+ throw new LogicException('Form has no name');
+ }
+
+ $values = $this->getRequest()->getParsedBody();
+
+ return isset($values[$this->uidElementName]) && $values[$this->uidElementName] === $name;
+ }
+}
diff --git a/vendor/ipl/web/src/Common/RedirectOption.php b/vendor/ipl/web/src/Common/RedirectOption.php
new file mode 100644
index 0000000..0d73ef8
--- /dev/null
+++ b/vendor/ipl/web/src/Common/RedirectOption.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace ipl\Web\Common;
+
+use ipl\Html\Contract\FormElement;
+use ipl\Html\Form;
+use LogicException;
+
+trait RedirectOption
+{
+ /**
+ * Create a form element to retrieve the redirect target upon form submit
+ *
+ * @return FormElement
+ */
+ protected function createRedirectOption()
+ {
+ /** @var Form $this */
+ return $this->createElement('hidden', 'redirect');
+ }
+
+ /**
+ * @see Form::getRedirectUrl()
+ */
+ public function getRedirectUrl()
+ {
+ /** @var Form $this */
+ $redirectOption = $this->getValue('redirect');
+ if (! $redirectOption) {
+ return parent::getRedirectUrl();
+ }
+
+ if (! $this->hasElement('CSRFToken') || ! $this->getElement('CSRFToken')->isValid()) {
+ throw new LogicException(
+ 'It is not safe to accept redirect targets from submit values without CSRF protection'
+ );
+ }
+
+ return $redirectOption;
+ }
+}
diff --git a/vendor/ipl/web/src/Common/StateBadges.php b/vendor/ipl/web/src/Common/StateBadges.php
new file mode 100644
index 0000000..e6e9cfd
--- /dev/null
+++ b/vendor/ipl/web/src/Common/StateBadges.php
@@ -0,0 +1,194 @@
+<?php
+
+namespace ipl\Web\Common;
+
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+use ipl\Stdlib\BaseFilter;
+use ipl\Stdlib\Filter;
+use ipl\Web\Filter\QueryString;
+use ipl\Web\Url;
+use ipl\Web\Widget\Link;
+use ipl\Web\Widget\StateBadge;
+
+/**
+ * @deprecated Use {@see \Icinga\Module\Icingadb\Common\StateBadges} instead.
+ */
+abstract class StateBadges extends BaseHtmlElement
+{
+ use BaseFilter;
+
+ /** @var object $item */
+ protected $item;
+
+ /** @var string */
+ protected $type;
+
+ /** @var string Prefix */
+ protected $prefix;
+
+ /** @var Url Badge link */
+ protected $url;
+
+ protected $tag = 'ul';
+
+ protected $defaultAttributes = ['class' => 'state-badges'];
+
+ /**
+ * Create a new widget for state badges
+ *
+ * @param object $item
+ */
+ public function __construct($item)
+ {
+ $this->item = $item;
+ $this->type = $this->getType();
+ $this->prefix = $this->getPrefix();
+ $this->url = $this->getBaseUrl();
+ }
+
+ /**
+ * Get the badge base URL
+ *
+ * @return Url
+ */
+ abstract protected function getBaseUrl(): Url;
+
+ /**
+ * Get the type of the items
+ *
+ * @return string
+ */
+ abstract protected function getType(): string;
+
+ /**
+ * Get the prefix for accessing state information
+ *
+ * @return string
+ */
+ abstract protected function getPrefix(): string;
+
+ /**
+ * Get the integer of the given state text
+ *
+ * @param string $state
+ *
+ * @return int
+ */
+ abstract protected function getStateInt(string $state): int;
+
+ /**
+ * Get the badge URL
+ *
+ * @return Url
+ */
+ public function getUrl(): Url
+ {
+ return $this->url;
+ }
+
+ /**
+ * Set the badge URL
+ *
+ * @param Url $url
+ *
+ * @return $this
+ */
+ public function setUrl(Url $url): self
+ {
+ $this->url = $url;
+
+ return $this;
+ }
+
+ /**
+ * Create a badge link
+ *
+ * @param mixed $content
+ * @param ?array $filter
+ *
+ * @return Link
+ */
+ public function createLink($content, array $filter = null): Link
+ {
+ $url = clone $this->getUrl();
+
+ $urlFilter = Filter::all();
+ if (! empty($filter)) {
+ foreach ($filter as $column => $value) {
+ $urlFilter->add(Filter::equal($column, $value));
+ }
+ }
+
+ if ($this->hasBaseFilter()) {
+ $urlFilter->add($this->getBaseFilter());
+ }
+
+ if (! $urlFilter->isEmpty()) {
+ $url->setFilter($urlFilter);
+ }
+
+ return new Link($content, $url);
+ }
+
+ /**
+ * Create a state bade
+ *
+ * @param string $state
+ *
+ * @return ?BaseHtmlElement
+ */
+ protected function createBadge(string $state)
+ {
+ $key = $this->prefix . "_{$state}";
+
+ if (isset($this->item->$key) && $this->item->$key) {
+ return Html::tag('li', $this->createLink(
+ new StateBadge($this->item->$key, $state),
+ [$this->type . '.state.soft_state' => $this->getStateInt($state)]
+ ));
+ }
+
+ return null;
+ }
+
+ /**
+ * Create a state group
+ *
+ * @param string $state
+ *
+ * @return ?BaseHtmlElement
+ */
+ protected function createGroup(string $state)
+ {
+ $content = [];
+ $handledKey = $this->prefix . "_{$state}_handled";
+ $unhandledKey = $this->prefix . "_{$state}_unhandled";
+
+ if (isset($this->item->$unhandledKey) && $this->item->$unhandledKey) {
+ $content[] = Html::tag('li', $this->createLink(
+ new StateBadge($this->item->$unhandledKey, $state),
+ [
+ $this->type . '.state.soft_state' => $this->getStateInt($state),
+ $this->type . '.state.is_handled' => 'n'
+ ]
+ ));
+ }
+
+ if (isset($this->item->$handledKey) && $this->item->$handledKey) {
+ $content[] = Html::tag('li', $this->createLink(
+ new StateBadge($this->item->$handledKey, $state, true),
+ [
+ $this->type . '.state.soft_state' => $this->getStateInt($state),
+ $this->type . '.state.is_handled' => 'y'
+ ]
+ ));
+ }
+
+ if (empty($content)) {
+ return null;
+ }
+
+ return Html::tag('li', Html::tag('ul', $content));
+ }
+}