diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:23:16 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:23:16 +0000 |
commit | 3e97c51418e6d27e9a81906f347fcb7c78e27d4f (patch) | |
tree | ee596ce1bc9840661386f96f9b8d1f919a106317 /vendor/gipfl/icingaweb2/src/Table | |
parent | Initial commit. (diff) | |
download | icingaweb2-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/icingaweb2/src/Table')
6 files changed, 830 insertions, 0 deletions
diff --git a/vendor/gipfl/icingaweb2/src/Table/Extension/MultiSelect.php b/vendor/gipfl/icingaweb2/src/Table/Extension/MultiSelect.php new file mode 100644 index 0000000..7a5a3ff --- /dev/null +++ b/vendor/gipfl/icingaweb2/src/Table/Extension/MultiSelect.php @@ -0,0 +1,29 @@ +<?php + +namespace gipfl\IcingaWeb2\Table\Extension; + +use gipfl\IcingaWeb2\Url; + +// Could also be a static method, MultiSelect::enable($table) +trait MultiSelect +{ + protected function enableMultiSelect($url, $sourceUrl, array $keys) + { + /** @var $table \ipl\Html\BaseHtmlElement */ + $table = $this; + $table->addAttributes([ + 'class' => 'multiselect' + ]); + + $prefix = 'data-icinga-multiselect'; + $multi = [ + "$prefix-url" => Url::fromPath($url), + "$prefix-controllers" => Url::fromPath($sourceUrl), + "$prefix-data" => implode(',', $keys), + ]; + + $table->addAttributes($multi); + + return $this; + } +} diff --git a/vendor/gipfl/icingaweb2/src/Table/Extension/QuickSearch.php b/vendor/gipfl/icingaweb2/src/Table/Extension/QuickSearch.php new file mode 100644 index 0000000..0f0cec3 --- /dev/null +++ b/vendor/gipfl/icingaweb2/src/Table/Extension/QuickSearch.php @@ -0,0 +1,74 @@ +<?php + +namespace gipfl\IcingaWeb2\Table\Extension; + +use gipfl\IcingaWeb2\Url; +use gipfl\IcingaWeb2\Widget\Controls; +use ipl\Html\BaseHtmlElement; +use ipl\Html\Html; + +trait QuickSearch +{ + /** @var BaseHtmlElement */ + private $quickSearchForm; + + public function getQuickSearch(BaseHtmlElement $parent, Url $url) + { + $this->requireQuickSearchForm($parent, $url); + $search = $url->getParam('q'); + return $search; + } + + private function requireQuickSearchForm(BaseHtmlElement $parent, Url $url) + { + if ($this->quickSearchForm === null) { + $this->quickSearchForm = $this->buildQuickSearchForm($parent, $url); + } + } + + private function buildQuickSearchForm(BaseHtmlElement $parent, Url $url) + { + $search = $url->getParam('q'); + + $form = Html::tag('form', [ + 'action' => $url->without(array('q', 'page', 'modifyFilter'))->getAbsoluteUrl(), + 'class' => ['gipfl-quicksearch'], + 'method' => 'GET' + ]); + + $form->add( + Html::tag('input', [ + 'type' => 'text', + 'name' => 'q', + 'title' => $this->translate('Search is simple! Try to combine multiple words'), + 'value' => $search, + 'placeholder' => $this->translate('Search...'), + 'class' => 'search' + ]) + ); + + $this->addQuickSearchToControls($parent, $form); + + return $form; + } + + protected function addQuickSearchToControls(BaseHtmlElement $parent, BaseHtmlElement $form) + { + if ($parent instanceof Controls) { + $title = $parent->getTitleElement(); + if ($title === null) { + $parent->prepend($form); + } else { + $input = $form->getFirst('input'); + $form->remove($input); + $title->add($input); + $form->add($title); + $parent->setTitleElement($form); + } + } else { + $parent->prepend($form); + } + + return $this; + } +} diff --git a/vendor/gipfl/icingaweb2/src/Table/Extension/ZfSortablePriority.php b/vendor/gipfl/icingaweb2/src/Table/Extension/ZfSortablePriority.php new file mode 100644 index 0000000..cb1eac6 --- /dev/null +++ b/vendor/gipfl/icingaweb2/src/Table/Extension/ZfSortablePriority.php @@ -0,0 +1,263 @@ +<?php + +namespace gipfl\IcingaWeb2\Table\Extension; + +use gipfl\IcingaWeb2\Table\ZfQueryBasedTable; +use gipfl\IcingaWeb2\IconHelper; +use gipfl\ZfDb\Exception\SelectException; +use gipfl\ZfDb\Select; +use Icinga\Web\Request; +use Icinga\Web\Response; +use ipl\Html\BaseHtmlElement; +use ipl\Html\Html; +use ipl\Html\HtmlString; +use RuntimeException; +use Zend_Db_Select_Exception as ZfDbSelectException; + +/** + * Trait ZfSortablePriority + * + * Assumes to run in a ZfQueryBasedTable + */ +trait ZfSortablePriority +{ + /** @var Request */ + protected $request; + + /** @var Response */ + protected $response; + + public function handleSortPriorityActions(Request $request, Response $response) + { + $this->request = $request; + $this->response = $response; + return $this; + } + + protected function reallyHandleSortPriorityActions() + { + $request = $this->request; + + if ($request->isPost() && $this->hasBeenSent($request)) { + // $this->fixPriorities(); + foreach (array_keys($request->getPost()) as $key) { + if (substr($key, 0, 8) === 'MOVE_UP_') { + $id = (int) substr($key, 8); + $this->moveRow($id, 'up'); + } + if (substr($key, 0, 10) === 'MOVE_DOWN_') { + $id = (int) substr($key, 10); + $this->moveRow($id, 'down'); + } + } + $this->response->redirectAndExit($request->getUrl()); + } + } + + protected function hasBeenSent(Request $request) + { + return $request->getPost('__FORM_NAME') === $this->getUniqueFormName(); + } + + protected function addSortPriorityButtons(BaseHtmlElement $tr, $row) + { + $tr->add( + Html::tag( + 'td', + null, + $this->createUpDownButtons($row->{$this->getKeyColumn()}) + ) + ); + + return $tr; + } + + protected function getKeyColumn() + { + if (isset($this->keyColumn)) { + return $this->keyColumn; + } else { + throw new RuntimeException( + 'ZfSortablePriority requires keyColumn' + ); + } + } + + protected function getPriorityColumn() + { + if (isset($this->priorityColumn)) { + return $this->priorityColumn; + } else { + throw new RuntimeException( + 'ZfSortablePriority requires priorityColumn' + ); + } + } + + protected function getPriorityColumns() + { + return [ + 'id' => $this->getKeyColumn(), + 'prio' => $this->getPriorityColumn() + ]; + } + + protected function moveRow($id, $direction) + { + /** @var $this ZfQueryBasedTable */ + $db = $this->db(); + /** @var $this ZfQueryBasedTable */ + $query = $this->getQuery(); + $tableParts = $this->getQueryPart(Select::FROM); + $alias = key($tableParts); + $table = $tableParts[$alias]['tableName']; + + $whereParts = $this->getQueryPart(Select::WHERE); + unset($query); + if (empty($whereParts)) { + $where = ''; + } else { + $where = ' AND ' . implode(' ', $whereParts); + } + + $prioCol = $this->getPriorityColumn(); + $keyCol = $this->getKeyColumn(); + $myPrio = (int) $db->fetchOne( + $db->select() + ->from($table, $prioCol) + ->where("$keyCol = ?", $id) + ); + + $op = $direction === 'up' ? '<' : '>'; + $sortDir = $direction === 'up' ? 'DESC' : 'ASC'; + $query = $db->select() + ->from([$alias => $table], $this->getPriorityColumns()) + ->where("$prioCol $op ?", $myPrio) + ->order("$prioCol $sortDir") + ->limit(1); + + if (! empty($whereParts)) { + $query->where(implode(' ', $whereParts)); + } + + $next = $db->fetchRow($query); + + if ($next) { + $sql = 'UPDATE %s %s' + . ' SET %s = CASE WHEN %s = %s THEN %d ELSE %d END' + . ' WHERE %s IN (%s, %s)'; + + $query = sprintf( + $sql, + $table, + $alias, + $prioCol, + $keyCol, + $id, + (int) $next->prio, + $myPrio, + $keyCol, + $id, + (int) $next->id + ) . $where; + + $db->query($query); + } + } + + protected function getSortPriorityTitle() + { + /** @var ZfQueryBasedTable $table */ + $table = $this; + + return Html::tag( + 'span', + ['title' => $table->translate('Change priority')], + $table->translate('Prio') + ); + } + + protected function createUpDownButtons($key) + { + /** @var ZfQueryBasedTable $table */ + $table = $this; + $up = $this->createIconButton( + "MOVE_UP_$key", + 'up-big', + $table->translate('Move up (raise priority)') + ); + $down = $this->createIconButton( + "MOVE_DOWN_$key", + 'down-big', + $table->translate('Move down (lower priority)') + ); + + if ($table->isOnFirstRow()) { + $up->getAttributes()->add('disabled', 'disabled'); + } + + if ($table->isOnLastRow()) { + $down->getAttributes()->add('disabled', 'disabled'); + } + + return [$down, $up]; + } + + protected function createIconButton($key, $icon, $title) + { + return Html::tag('input', [ + 'type' => 'submit', + 'class' => 'icon-button', + 'name' => $key, + 'title' => $title, + 'value' => IconHelper::instance()->iconCharacter($icon) + ]); + } + + protected function getUniqueFormName() + { + $parts = explode('\\', get_class($this)); + return end($parts); + } + + protected function renderWithSortableForm() + { + if ($this->request === null) { + return parent::render(); + } + $this->reallyHandleSortPriorityActions(); + + $url = $this->request->getUrl(); + // TODO: No margin for form + $form = Html::tag('form', [ + 'action' => $url->getAbsoluteUrl(), + 'method' => 'POST' + ], [ + Html::tag('input', [ + 'type' => 'hidden', + 'name' => '__FORM_NAME', + 'value' => $this->getUniqueFormName() + ]), + new HtmlString(parent::render()) + ]); + + return $form->render(); + } + + protected function getQueryPart($part) + { + /** @var ZfQueryBasedTable $table */ + $table = $this; + /** @var Select|\Zend_Db_Select $query */ + $query = $table->getQuery(); + try { + return $query->getPart($part); + } catch (SelectException $e) { + // Will not happen if $part is correct. + throw new RuntimeException($e); + } catch (ZfDbSelectException $e) { + // Will not happen if $part is correct. + throw new RuntimeException($e); + } + } +} diff --git a/vendor/gipfl/icingaweb2/src/Table/QueryBasedTable.php b/vendor/gipfl/icingaweb2/src/Table/QueryBasedTable.php new file mode 100644 index 0000000..e9281c7 --- /dev/null +++ b/vendor/gipfl/icingaweb2/src/Table/QueryBasedTable.php @@ -0,0 +1,281 @@ +<?php + +namespace gipfl\IcingaWeb2\Table; + +use Countable; +use gipfl\Format\LocalDateFormat; +use gipfl\IcingaWeb2\Data\Paginatable; +use gipfl\IcingaWeb2\Zf1\Db\FilterRenderer; +use gipfl\IcingaWeb2\Table\Extension\QuickSearch; +use gipfl\IcingaWeb2\Url; +use gipfl\IcingaWeb2\Widget\ControlsAndContent; +use gipfl\IcingaWeb2\Widget\Paginator; +use gipfl\Translation\TranslationHelper; +use Icinga\Application\Benchmark; +use Icinga\Data\Filter\Filter; +use ipl\Html\Table; + +abstract class QueryBasedTable extends Table implements Countable +{ + use TranslationHelper; + use QuickSearch; + + protected $defaultAttributes = [ + 'class' => ['common-table', 'table-row-selectable'], + 'data-base-target' => '_next', + ]; + + private $fetchedRows; + + private $firstRow; + + private $lastRow = false; + + private $rowNumber; + + private $rowNumberOnPage; + + protected $lastDay; + + /** @var Paginator|null Will usually be defined at rendering time */ + protected $paginator; + + private $isUsEnglish; + + private $dateFormatter; + + protected $searchColumns = []; + + /** + * @return Paginatable + */ + abstract protected function getPaginationAdapter(); + + abstract public function getQuery(); + + public function getPaginator(Url $url) + { + return new Paginator( + $this->getPaginationAdapter(), + $url + ); + } + + public function count() + { + return $this->getPaginationAdapter()->count(); + } + + public function applyFilter(Filter $filter) + { + FilterRenderer::applyToQuery($filter, $this->getQuery()); + return $this; + } + + protected function getSearchColumns() + { + return $this->searchColumns; + } + + public function search($search) + { + if (! empty($search)) { + $query = $this->getQuery(); + $columns = $this->getSearchColumns(); + if (strpos($search, ' ') === false) { + $filter = Filter::matchAny(); + foreach ($columns as $column) { + $filter->addFilter(Filter::expression($column, '=', "*$search*")); + } + } else { + $filter = Filter::matchAll(); + foreach (explode(' ', $search) as $s) { + $sub = Filter::matchAny(); + foreach ($columns as $column) { + $sub->addFilter(Filter::expression($column, '=', "*$s*")); + } + $filter->addFilter($sub); + } + } + + FilterRenderer::applyToQuery($filter, $query); + } + + return $this; + } + + abstract protected function prepareQuery(); + + public function renderContent() + { + $titleColumns = $this->renderTitleColumns(); + if ($titleColumns) { + $this->getHeader()->add($titleColumns); + } + $this->fetchRows(); + + return parent::renderContent(); + } + + protected function renderTitleColumns() + { + // TODO: drop this + if (method_exists($this, 'getColumnsToBeRendered')) { + $columns = $this->getColumnsToBeRendered(); + if (isset($columns) && count($columns)) { + return static::row($columns, null, 'th'); + } + } + + return null; + } + + protected function splitByDay($timestamp) + { + $this->renderDayIfNew((int) $timestamp); + } + + public function isOnFirstPage() + { + if ($this->paginator === null) { + // No paginator? Then there should be only a single page + return true; + } + + return $this->paginator->getCurrentPage() === 1; + } + + public function isOnFirstRow() + { + return $this->firstRow === true; + } + + public function isOnLastRow() + { + return $this->lastRow === true; + } + + protected function fetchRows() + { + $firstPage = $this->isOnFirstPage(); + $this->rowNumberOnPage = 0; + $this->rowNumber = $this->getPaginationAdapter()->getOffset(); + $lastRow = count($this); + foreach ($this->fetch() as $row) { + $this->rowNumber++; + $this->rowNumberOnPage++; + if (null === $this->firstRow) { + if ($firstPage) { + $this->firstRow = true; + } else { + $this->firstRow = false; + } + } elseif (true === $this->firstRow) { + $this->firstRow = false; + } + if ($lastRow === $this->rowNumber) { + $this->lastRow = true; + } + // Hint: do not fetch the body first, the row might want to replace it + $tr = $this->renderRow($row); + $this->add($tr); + } + } + + protected function renderRow($row) + { + return $this::row([$row]); + } + + /** + * @deprecated + * @return bool + */ + protected function isUsEnglish() + { + if ($this->isUsEnglish === null) { + $this->isUsEnglish = in_array(setlocale(LC_ALL, 0), ['en_US', 'en_US.UTF-8', 'C']); + } + + return $this->isUsEnglish; + } + + /** + * @param int $timestamp + */ + protected function renderDayIfNew($timestamp) + { + $day = $this->getDateFormatter()->getFullDay($timestamp); + + if ($this->lastDay !== $day) { + $this->nextHeader()->add( + $this::th($day, [ + 'colspan' => 2, + 'class' => 'table-header-day' + ]) + ); + + $this->lastDay = $day; + $this->nextBody(); + } + } + + abstract protected function fetchQueryRows(); + + public function fetch() + { + $parts = explode('\\', get_class($this)); + $name = end($parts); + Benchmark::measure("Fetching data for $name table"); + $rows = $this->fetchQueryRows(); + $this->fetchedRows = count($rows); + Benchmark::measure("Fetched $this->fetchedRows rows for $name table"); + + return $rows; + } + + protected function initializeOptionalQuickSearch(ControlsAndContent $controller) + { + $columns = $this->getSearchColumns(); + if (! empty($columns)) { + $this->search( + $this->getQuickSearch( + $controller->controls(), + $controller->url() + ) + ); + } + } + + /** + * @param ControlsAndContent $controller + * @return $this + */ + public function renderTo(ControlsAndContent $controller) + { + $url = $controller->url(); + $c = $controller->content(); + $this->paginator = $this->getPaginator($url); + $this->initializeOptionalQuickSearch($controller); + $controller->actions()->add($this->paginator); + $c->add($this); + + // TODO: move elsewhere + if (method_exists($this, 'dumpSqlQuery')) { + if ($url->getParam('format') === 'sql') { + $c->prepend($this->dumpSqlQuery($url)); + } + } + + return $this; + } + + protected function getDateFormatter() + { + if ($this->dateFormatter === null) { + $this->dateFormatter = new LocalDateFormat(); + } + + return $this->dateFormatter; + } +} diff --git a/vendor/gipfl/icingaweb2/src/Table/SimpleQueryBasedTable.php b/vendor/gipfl/icingaweb2/src/Table/SimpleQueryBasedTable.php new file mode 100644 index 0000000..8d6015a --- /dev/null +++ b/vendor/gipfl/icingaweb2/src/Table/SimpleQueryBasedTable.php @@ -0,0 +1,34 @@ +<?php + +namespace gipfl\IcingaWeb2\Table; + +use Icinga\Data\SimpleQuery; +use gipfl\IcingaWeb2\Data\SimpleQueryPaginationAdapter; + +abstract class SimpleQueryBasedTable extends QueryBasedTable +{ + /** @var SimpleQuery */ + private $query; + + protected function getPaginationAdapter() + { + return new SimpleQueryPaginationAdapter($this->getQuery()); + } + + protected function fetchQueryRows() + { + return $this->query->fetchAll(); + } + + /** + * @return SimpleQuery + */ + public function getQuery() + { + if ($this->query === null) { + $this->query = $this->prepareQuery(); + } + + return $this->query; + } +} diff --git a/vendor/gipfl/icingaweb2/src/Table/ZfQueryBasedTable.php b/vendor/gipfl/icingaweb2/src/Table/ZfQueryBasedTable.php new file mode 100644 index 0000000..8a421d5 --- /dev/null +++ b/vendor/gipfl/icingaweb2/src/Table/ZfQueryBasedTable.php @@ -0,0 +1,149 @@ +<?php + +namespace gipfl\IcingaWeb2\Table; + +use gipfl\IcingaWeb2\Link; +use gipfl\IcingaWeb2\Url; +use gipfl\IcingaWeb2\Widget\ControlsAndContent; +use gipfl\IcingaWeb2\Zf1\Db\FilterRenderer; +use gipfl\IcingaWeb2\Zf1\Db\SelectPaginationAdapter; +use gipfl\ZfDb\Adapter\Adapter as Db; +use gipfl\ZfDb\Select as DbSelect; +use Icinga\Data\Db\DbConnection; +use Icinga\Data\Filter\Filter; +use ipl\Html\DeferredText; +use ipl\Html\Html; +use LogicException; +use Zend_Db_Adapter_Abstract as DbAdapter; + +abstract class ZfQueryBasedTable extends QueryBasedTable +{ + /** @var ?DbConnection */ + private $connection; + + /** @var DbAdapter|Db */ + private $db; + + private $query; + + private $paginationAdapter; + + public function __construct($db) + { + if ($db instanceof Db || $db instanceof DbAdapter) { + $this->db = $db; + } elseif ($db instanceof DbConnection) { + $this->connection = $db; + $this->db = $db->getDbAdapter(); + } else { + throw new LogicException(sprintf( + 'Unable to deal with %s db class', + get_class($db) + )); + } + } + + public static function show(ControlsAndContent $controller, DbConnection $db) + { + $table = new static($db); + $table->renderTo($controller); + } + + public function getCountQuery() + { + return $this->getPaginationAdapter()->getCountQuery(); + } + + protected function getPaginationAdapter() + { + if ($this->paginationAdapter === null) { + $this->paginationAdapter = new SelectPaginationAdapter($this->getQuery()); + } + + return $this->paginationAdapter; + } + + public function applyFilter(Filter $filter) + { + FilterRenderer::applyToQuery($filter, $this->getQuery()); + return $this; + } + + public function search($search) + { + if (! empty($search)) { + $query = $this->getQuery(); + $columns = $this->getSearchColumns(); + if (strpos($search, ' ') === false) { + $filter = Filter::matchAny(); + foreach ($columns as $column) { + $filter->addFilter(Filter::expression($column, '=', "*$search*")); + } + } else { + $filter = Filter::matchAll(); + foreach (explode(' ', $search) as $s) { + $sub = Filter::matchAny(); + foreach ($columns as $column) { + $sub->addFilter(Filter::expression($column, '=', "*$s*")); + } + $filter->addFilter($sub); + } + } + + FilterRenderer::applyToQuery($filter, $query); + } + + return $this; + } + + protected function fetchQueryRows() + { + return $this->db->fetchAll($this->getQuery()); + } + + /** + * @deprecated Might be null, we'll fade it out + * @return ?DbConnection + */ + public function connection() + { + return $this->connection; + } + + public function db() + { + return $this->db; + } + + /** + * @return DbSelect|\Zend_Db_Select + */ + public function getQuery() + { + if ($this->query === null) { + $this->query = $this->prepareQuery(); + } + + return $this->query; + } + + public function dumpSqlQuery(Url $url) + { + $self = $this; + return Html::tag('div', ['class' => 'sql-dump'], [ + Link::create('[ close ]', $url->without('format')), + Html::tag('h3', null, $this->translate('SQL Query')), + Html::tag('pre', null, new DeferredText( + function () use ($self) { + return wordwrap($self->getQuery()); + } + )), + Html::tag('h3', null, $this->translate('Count Query')), + Html::tag('pre', null, new DeferredText( + function () use ($self) { + return wordwrap($self->getCountQuery()); + } + )), + ]); + } +} |