diff options
Diffstat (limited to 'vendor/ipl/web/src/Common')
-rw-r--r-- | vendor/ipl/web/src/Common/BaseItemList.php | 73 | ||||
-rw-r--r-- | vendor/ipl/web/src/Common/BaseItemTable.php | 88 | ||||
-rw-r--r-- | vendor/ipl/web/src/Common/BaseListItem.php | 145 | ||||
-rw-r--r-- | vendor/ipl/web/src/Common/BaseOrderedItemList.php | 31 | ||||
-rw-r--r-- | vendor/ipl/web/src/Common/BaseOrderedListItem.php | 42 | ||||
-rw-r--r-- | vendor/ipl/web/src/Common/BaseTableRowItem.php | 119 | ||||
-rw-r--r-- | vendor/ipl/web/src/Common/BaseTarget.php | 36 | ||||
-rw-r--r-- | vendor/ipl/web/src/Common/Card.php | 59 | ||||
-rw-r--r-- | vendor/ipl/web/src/Common/CsrfCounterMeasure.php | 48 | ||||
-rw-r--r-- | vendor/ipl/web/src/Common/FormUid.php | 59 | ||||
-rw-r--r-- | vendor/ipl/web/src/Common/RedirectOption.php | 41 | ||||
-rw-r--r-- | vendor/ipl/web/src/Common/StateBadges.php | 194 |
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)); + } +} |