diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:46:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:46:43 +0000 |
commit | 3e02d5aff85babc3ffbfcf52313f2108e313aa23 (patch) | |
tree | b01f3923360c20a6a504aff42d45670c58af3ec5 /library/Icinga/Web/View | |
parent | Initial commit. (diff) | |
download | icingaweb2-3e02d5aff85babc3ffbfcf52313f2108e313aa23.tar.xz icingaweb2-3e02d5aff85babc3ffbfcf52313f2108e313aa23.zip |
Adding upstream version 2.12.1.upstream/2.12.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/Icinga/Web/View')
-rw-r--r-- | library/Icinga/Web/View/AppHealth.php | 89 | ||||
-rw-r--r-- | library/Icinga/Web/View/Helper/IcingaCheckbox.php | 30 | ||||
-rw-r--r-- | library/Icinga/Web/View/PrivilegeAudit.php | 622 | ||||
-rw-r--r-- | library/Icinga/Web/View/helpers/format.php | 72 | ||||
-rw-r--r-- | library/Icinga/Web/View/helpers/generic.php | 15 | ||||
-rw-r--r-- | library/Icinga/Web/View/helpers/string.php | 36 | ||||
-rw-r--r-- | library/Icinga/Web/View/helpers/url.php | 158 |
7 files changed, 1022 insertions, 0 deletions
diff --git a/library/Icinga/Web/View/AppHealth.php b/library/Icinga/Web/View/AppHealth.php new file mode 100644 index 0000000..c66ca05 --- /dev/null +++ b/library/Icinga/Web/View/AppHealth.php @@ -0,0 +1,89 @@ +<?php +/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +namespace Icinga\Web\View; + +use Icinga\Application\Hook\HealthHook; +use ipl\Html\FormattedString; +use ipl\Html\HtmlElement; +use ipl\Html\Table; +use ipl\Web\Common\BaseTarget; +use ipl\Web\Widget\Link; +use Traversable; + +class AppHealth extends Table +{ + use BaseTarget; + + protected $defaultAttributes = ['class' => ['app-health', 'common-table', 'table-row-selectable']]; + + /** @var Traversable */ + protected $data; + + public function __construct(Traversable $data) + { + $this->data = $data; + + $this->setBaseTarget('_next'); + } + + protected function assemble() + { + foreach ($this->data as $row) { + $this->add(Table::tr([ + Table::th(HtmlElement::create('span', ['class' => [ + 'ball', + 'ball-size-xl', + $this->getStateClass($row->state) + ]])), + Table::td([ + new HtmlElement('header', null, FormattedString::create( + t('%s by %s is %s', '<check> by <module> is <state-text>'), + $row->url + ? new Link(HtmlElement::create('span', null, $row->name), $row->url) + : HtmlElement::create('span', null, $row->name), + HtmlElement::create('span', null, $row->module), + HtmlElement::create('span', null, $this->getStateText($row->state)) + )), + HtmlElement::create('section', null, $row->message) + ]) + ])); + } + } + + protected function getStateClass($state) + { + if ($state === null) { + $state = HealthHook::STATE_UNKNOWN; + } + + switch ($state) { + case HealthHook::STATE_OK: + return 'state-ok'; + case HealthHook::STATE_WARNING: + return 'state-warning'; + case HealthHook::STATE_CRITICAL: + return 'state-critical'; + case HealthHook::STATE_UNKNOWN: + return 'state-unknown'; + } + } + + protected function getStateText($state) + { + if ($state === null) { + $state = t('UNKNOWN'); + } + + switch ($state) { + case HealthHook::STATE_OK: + return t('OK'); + case HealthHook::STATE_WARNING: + return t('WARNING'); + case HealthHook::STATE_CRITICAL: + return t('CRITICAL'); + case HealthHook::STATE_UNKNOWN: + return t('UNKNOWN'); + } + } +} diff --git a/library/Icinga/Web/View/Helper/IcingaCheckbox.php b/library/Icinga/Web/View/Helper/IcingaCheckbox.php new file mode 100644 index 0000000..07cf01f --- /dev/null +++ b/library/Icinga/Web/View/Helper/IcingaCheckbox.php @@ -0,0 +1,30 @@ +<?php +/* Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */ + +namespace Icinga\Web\View\Helper; + +class IcingaCheckbox extends \Zend_View_Helper_FormCheckbox +{ + public function icingaCheckbox($name, $value = null, $attribs = null, array $checkedOptions = null) + { + if (! isset($attribs['id'])) { + $attribs['id'] = $this->view->protectId('icingaCheckbox_' . $name); + } + + $attribs['class'] = (isset($attribs['class']) ? $attribs['class'] . ' ' : '') . 'sr-only'; + $html = parent::formCheckbox($name, $value, $attribs, $checkedOptions); + + $class = 'toggle-switch'; + if (isset($attribs['disabled'])) { + $class .= ' disabled'; + } + + return $html + . '<label for="' + . $attribs['id'] + . '" aria-hidden="true"' + . ' class="' + . $class + . '"><span class="toggle-slider"></span></label>'; + } +} diff --git a/library/Icinga/Web/View/PrivilegeAudit.php b/library/Icinga/Web/View/PrivilegeAudit.php new file mode 100644 index 0000000..fcb4083 --- /dev/null +++ b/library/Icinga/Web/View/PrivilegeAudit.php @@ -0,0 +1,622 @@ +<?php +/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +namespace Icinga\Web\View; + +use Icinga\Authentication\Role; +use Icinga\Forms\Security\RoleForm; +use Icinga\Util\StringHelper; +use ipl\Html\Attributes; +use ipl\Html\BaseHtmlElement; +use ipl\Html\HtmlElement; +use ipl\Html\HtmlString; +use ipl\Html\Text; +use ipl\Stdlib\Filter; +use ipl\Web\Common\BaseTarget; +use ipl\Web\Filter\QueryString; +use ipl\Web\Url; +use ipl\Web\Widget\Icon; +use ipl\Web\Widget\Link; + +class PrivilegeAudit extends BaseHtmlElement +{ + use BaseTarget; + + /** @var string */ + const UNRESTRICTED_PERMISSION = 'unrestricted'; + + protected $tag = 'ul'; + + protected $defaultAttributes = ['class' => 'privilege-audit']; + + /** @var Role[] */ + protected $roles; + + public function __construct(array $roles) + { + $this->roles = $roles; + $this->setBaseTarget('_next'); + } + + protected function auditPermission($permission) + { + $grantedBy = []; + $refusedBy = []; + foreach ($this->roles as $role) { + if ($permission === self::UNRESTRICTED_PERMISSION) { + if ($role->isUnrestricted()) { + $grantedBy[] = $role->getName(); + } + } elseif ($role->denies($permission)) { + $refusedBy[] = $role->getName(); + } elseif ($role->grants($permission, false, false)) { + $grantedBy[] = $role->getName(); + } + } + + $header = new HtmlElement('summary'); + if (! empty($refusedBy)) { + $header->add([ + new Icon('times-circle', ['class' => 'refused']), + count($refusedBy) > 2 + ? sprintf( + tp( + 'Refused by %s and %s as well as one other', + 'Refused by %s and %s as well as %d others', + count($refusedBy) - 2 + ), + $refusedBy[0], + $refusedBy[1], + count($refusedBy) - 2 + ) + : sprintf( + tp('Refused by %s', 'Refused by %s and %s', count($refusedBy)), + ...$refusedBy + ) + ]); + } elseif (! empty($grantedBy)) { + $header->add([ + new Icon('check-circle', ['class' => 'granted']), + count($grantedBy) > 2 + ? sprintf( + tp( + 'Granted by %s and %s as well as one other', + 'Granted by %s and %s as well as %d others', + count($grantedBy) - 2 + ), + $grantedBy[0], + $grantedBy[1], + count($grantedBy) - 2 + ) + : sprintf( + tp('Granted by %s', 'Granted by %s and %s', count($grantedBy)), + ...$grantedBy + ) + ]); + } else { + $header->add([new Icon('minus-circle'), t('Not granted or refused by any role')]); + } + + $vClass = null; + $rolePaths = []; + foreach (array_reverse($this->roles) as $role) { + if (! in_array($role->getName(), $refusedBy, true) && ! in_array($role->getName(), $grantedBy, true)) { + continue; + } + + /** @var Role[] $rolesReversed */ + $rolesReversed = []; + + do { + array_unshift($rolesReversed, $role); + } while (($role = $role->getParent()) !== null); + + $path = new HtmlElement('ol'); + + $class = null; + $setInitiator = false; + foreach ($rolesReversed as $role) { + $granted = false; + $refused = false; + $icon = new Icon('minus-circle'); + if ($permission === self::UNRESTRICTED_PERMISSION) { + if ($role->isUnrestricted()) { + $granted = true; + $icon = new Icon('check-circle', ['class' => 'granted']); + } + } elseif ($role->denies($permission, true)) { + $refused = true; + $icon = new Icon('times-circle', ['class' => 'refused']); + } elseif ($role->grants($permission, true, false)) { + $granted = true; + $icon = new Icon('check-circle', ['class' => 'granted']); + } + + $connector = null; + if ($role->getParent() !== null) { + $connector = HtmlElement::create('li', ['class' => ['connector', $class]]); + if ($setInitiator) { + $setInitiator = false; + $connector->getAttributes()->add('class', 'initiator'); + } + + $path->prependHtml($connector); + } + + $path->prependHtml(new HtmlElement('li', Attributes::create([ + 'class' => ['role', $class], + 'title' => $role->getName() + ]), new Link([$icon, $role->getName()], Url::fromPath('role/edit', ['role' => $role->getName()])))); + + if ($refused) { + $setInitiator = $class !== 'refused'; + $class = 'refused'; + } elseif ($granted) { + $setInitiator = $class === null; + $class = $class ?: 'granted'; + } + } + + if ($vClass === null || $vClass === 'granted') { + $vClass = $class; + } + + array_unshift($rolePaths, $path->prepend([ + empty($rolePaths) ? null : HtmlElement::create('li', ['class' => ['vertical-line', $vClass]]), + new HtmlElement('li', Attributes::create(['class' => [ + 'connector', + $class, + $setInitiator ? 'initiator' : null + ]])) + ])); + } + + if (empty($rolePaths)) { + return [ + empty($refusedBy) ? (empty($grantedBy) ? null : true) : false, + new HtmlElement( + 'div', + Attributes::create(['class' => 'inheritance-paths']), + $header->setTag('div') + ) + ]; + } + + return [ + empty($refusedBy) ? (empty($grantedBy) ? null : true) : false, + HtmlElement::create('details', [ + 'class' => ['collapsible', 'inheritance-paths'], + 'data-no-persistence' => true, + 'open' => getenv('ICINGAWEB_EXPORT_FORMAT') === 'pdf' + ], [ + $header->addAttributes(['class' => 'collapsible-control']), + $rolePaths + ]) + ]; + } + + protected function auditRestriction($restriction) + { + $restrictedBy = []; + $restrictions = []; + foreach ($this->roles as $role) { + if ($role->isUnrestricted()) { + $restrictedBy = []; + $restrictions = []; + break; + } + + foreach ($this->collectRestrictions($role, $restriction) as $role => $roleRestriction) { + $restrictedBy[] = $role; + $restrictions[] = $roleRestriction; + } + } + + $header = new HtmlElement('summary'); + if (! empty($restrictedBy)) { + $header->add([ + new Icon('filter', ['class' => 'restricted']), + count($restrictedBy) > 2 + ? sprintf( + tp( + 'Restricted by %s and %s as well as one other', + 'Restricted by %s and %s as well as %d others', + count($restrictedBy) - 2 + ), + $restrictedBy[0]->getName(), + $restrictedBy[1]->getName(), + count($restrictedBy) - 2 + ) + : sprintf( + tp('Restricted by %s', 'Restricted by %s and %s', count($restrictedBy)), + ...array_map(function ($role) { + return $role->getName(); + }, $restrictedBy) + ) + ]); + } else { + $header->add([new Icon('filter'), t('Not restricted by any role')]); + } + + $roles = []; + if (! empty($restrictions) && count($restrictions) > 1) { + list($combinedRestrictions, $combinedLinks) = $this->createRestrictionLinks($restriction, $restrictions); + $roles[] = HtmlElement::create('li', null, [ + new HtmlElement( + 'div', + Attributes::create(['class' => 'flex-overflow']), + HtmlElement::create('span', [ + 'class' => 'role', + 'title' => t('All roles combined') + ], join(' | ', array_map(function ($role) { + return $role->getName(); + }, $restrictedBy))), + HtmlElement::create('code', ['class' => 'restriction'], $combinedRestrictions) + ), + $combinedLinks ? new HtmlElement( + 'div', + Attributes::create(['class' => 'previews']), + HtmlElement::create('em', null, t('Previews:')), + $combinedLinks + ) : null + ]); + } + + foreach ($restrictedBy as $role) { + list($roleRestriction, $restrictionLinks) = $this->createRestrictionLinks( + $restriction, + [$role->getRestrictions($restriction)] + ); + + $roles[] = HtmlElement::create('li', null, [ + new HtmlElement( + 'div', + Attributes::create(['class' => 'flex-overflow']), + new Link($role->getName(), Url::fromPath('role/edit', ['role' => $role->getName()]), [ + 'class' => 'role', + 'title' => $role->getName() + ]), + HtmlElement::create('code', ['class' => 'restriction'], $roleRestriction) + ), + $restrictionLinks ? new HtmlElement( + 'div', + Attributes::create(['class' => 'previews']), + HtmlElement::create('em', null, t('Previews:')), + $restrictionLinks + ) : null + ]); + } + + if (empty($roles)) { + return [ + ! empty($restrictedBy), + new HtmlElement( + 'div', + Attributes::create(['class' => 'restrictions']), + $header->setTag('div') + ) + ]; + } + + return [ + ! empty($restrictedBy), + new HtmlElement( + 'details', + Attributes::create([ + 'class' => ['collapsible', 'restrictions'], + 'data-no-persistence' => true, + 'open' => getenv('ICINGAWEB_EXPORT_FORMAT') === 'pdf' + ]), + $header->addAttributes(['class' => 'collapsible-control']), + new HtmlElement('ul', null, ...$roles) + ) + ]; + } + + protected function assemble() + { + list($permissions, $restrictions) = RoleForm::collectProvidedPrivileges(); + list($wildcardState, $wildcardAudit) = $this->auditPermission('*'); + list($unrestrictedState, $unrestrictedAudit) = $this->auditPermission(self::UNRESTRICTED_PERMISSION); + + $this->addHtml(new HtmlElement( + 'li', + null, + new HtmlElement( + 'details', + Attributes::create([ + 'class' => ['collapsible', 'privilege-section'], + 'open' => ($wildcardState || $unrestrictedState) && getenv('ICINGAWEB_EXPORT_FORMAT') === 'pdf' + ]), + new HtmlElement( + 'summary', + Attributes::create(['class' => [ + 'collapsible-control', // Helps JS, improves performance a bit + ]]), + new HtmlElement('span', null, Text::create(t('Administrative Privileges'))), + HtmlElement::create( + 'span', + ['class' => 'audit-preview'], + $wildcardState || $unrestrictedState + ? new Icon('check-circle', ['class' => 'granted']) + : null + ), + new Icon('angles-down', ['class' => 'collapse-icon']), + new Icon('angles-left', ['class' => 'expand-icon']) + ), + new HtmlElement( + 'ol', + Attributes::create(['class' => 'privilege-list']), + new HtmlElement( + 'li', + null, + HtmlElement::create('p', ['class' => 'privilege-label'], t('Administrative Access')), + HtmlElement::create('div', ['class' => 'spacer']), + $wildcardAudit + ), + new HtmlElement( + 'li', + null, + HtmlElement::create('p', ['class' => 'privilege-label'], t('Unrestricted Access')), + HtmlElement::create('div', ['class' => 'spacer']), + $unrestrictedAudit + ) + ) + ) + )); + + $privilegeSources = array_unique(array_merge(array_keys($permissions), array_keys($restrictions))); + foreach ($privilegeSources as $source) { + $anythingGranted = false; + $anythingRefused = false; + $anythingRestricted = false; + + $permissionList = new HtmlElement('ol', Attributes::create(['class' => 'privilege-list'])); + foreach (isset($permissions[$source]) ? $permissions[$source] : [] as $permission => $metaData) { + list($permissionState, $permissionAudit) = $this->auditPermission($permission); + if ($permissionState !== null) { + if ($permissionState) { + $anythingGranted = true; + } else { + $anythingRefused = true; + } + } + + $permissionList->addHtml(new HtmlElement( + 'li', + null, + HtmlElement::create( + 'p', + ['class' => 'privilege-label'], + isset($metaData['label']) + ? $metaData['label'] + : array_map(function ($segment) { + return $segment[0] === '/' ? [ + // Adds a zero-width char after each slash to help browsers break onto newlines + new HtmlString('/​'), + HtmlElement::create('span', ['class' => 'no-wrap'], substr($segment, 1)) + ] : HtmlElement::create('em', null, $segment); + }, preg_split( + '~(/[^/]+)~', + $permission, + -1, + PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY + )) + ), + new HtmlElement('div', Attributes::create(['class' => 'spacer'])), + $permissionAudit + )); + } + + $restrictionList = new HtmlElement('ol', Attributes::create(['class' => 'privilege-list'])); + foreach (isset($restrictions[$source]) ? $restrictions[$source] : [] as $restriction => $metaData) { + list($restrictionState, $restrictionAudit) = $this->auditRestriction($restriction); + if ($restrictionState) { + $anythingRestricted = true; + } + + $restrictionList->addHtml(new HtmlElement( + 'li', + null, + HtmlElement::create( + 'p', + ['class' => 'privilege-label'], + isset($metaData['label']) + ? $metaData['label'] + : array_map(function ($segment) { + return $segment[0] === '/' ? [ + // Adds a zero-width char after each slash to help browsers break onto newlines + new HtmlString('/​'), + HtmlElement::create('span', ['class' => 'no-wrap'], substr($segment, 1)) + ] : HtmlElement::create('em', null, $segment); + }, preg_split( + '~(/[^/]+)~', + $restriction, + -1, + PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY + )) + ), + new HtmlElement('div', Attributes::create(['class' => 'spacer'])), + $restrictionAudit + )); + } + + if ($source === 'application') { + $label = 'Icinga Web 2'; + } else { + $label = [$source, ' ', HtmlElement::create('em', null, t('Module'))]; + } + + $this->addHtml(new HtmlElement( + 'li', + null, + HtmlElement::create('details', [ + 'class' => ['collapsible', 'privilege-section'], + 'open' => ($anythingGranted || $anythingRefused || $anythingRestricted) + && getenv('ICINGAWEB_EXPORT_FORMAT') === 'pdf' + ], [ + new HtmlElement( + 'summary', + Attributes::create(['class' => [ + 'collapsible-control', // Helps JS, improves performance a bit + ]]), + HtmlElement::create('span', null, $label), + HtmlElement::create('span', ['class' => 'audit-preview'], [ + $anythingGranted ? new Icon('check-circle', ['class' => 'granted']) : null, + $anythingRefused ? new Icon('times-circle', ['class' => 'refused']) : null, + $anythingRestricted ? new Icon('filter', ['class' => 'restricted']) : null + ]), + new Icon('angles-down', ['class' => 'collapse-icon']), + new Icon('angles-left', ['class' => 'expand-icon']) + ), + $permissionList->isEmpty() ? null : [ + HtmlElement::create('h4', null, t('Permissions')), + $permissionList + ], + $restrictionList->isEmpty() ? null : [ + HtmlElement::create('h4', null, t('Restrictions')), + $restrictionList + ] + ]) + )); + } + } + + private function collectRestrictions(Role $role, $restrictionName) + { + do { + $restriction = $role->getRestrictions($restrictionName); + if ($restriction) { + yield $role => $restriction; + } + } while (($role = $role->getParent()) !== null); + } + + private function createRestrictionLinks($restrictionName, array $restrictions) + { + // TODO: Remove this hardcoded mess. Do this based on the restriction's meta data + switch ($restrictionName) { + case 'icingadb/filter/objects': + $filterString = join('|', $restrictions); + $list = new HtmlElement( + 'ul', + Attributes::create(['class' => 'links']), + new HtmlElement('li', null, new Link( + 'icingadb/hosts', + Url::fromPath('icingadb/hosts')->setQueryString($filterString) + )), + new HtmlElement('li', null, new Link( + 'icingadb/services', + Url::fromPath('icingadb/services')->setQueryString($filterString) + )), + new HtmlElement('li', null, new Link( + 'icingadb/hostgroups', + Url::fromPath('icingadb/hostgroups')->setQueryString($filterString) + )), + new HtmlElement('li', null, new Link( + 'icingadb/servicegroups', + Url::fromPath('icingadb/servicegroups')->setQueryString($filterString) + )) + ); + + break; + case 'icingadb/filter/hosts': + $filterString = join('|', $restrictions); + $list = new HtmlElement( + 'ul', + Attributes::create(['class' => 'links']), + new HtmlElement('li', null, new Link( + 'icingadb/hosts', + Url::fromPath('icingadb/hosts')->setQueryString($filterString) + )), + new HtmlElement('li', null, new Link( + 'icingadb/services', + Url::fromPath('icingadb/services')->setQueryString($filterString) + )) + ); + + break; + case 'icingadb/filter/services': + $filterString = join('|', $restrictions); + $list = new HtmlElement( + 'ul', + Attributes::create(['class' => 'links']), + new HtmlElement('li', null, new Link( + 'icingadb/services', + Url::fromPath('icingadb/services')->setQueryString($filterString) + )) + ); + + break; + case 'monitoring/filter/objects': + $filterString = join('|', $restrictions); + $list = new HtmlElement( + 'ul', + Attributes::create(['class' => 'links']), + new HtmlElement('li', null, new Link( + 'monitoring/list/hosts', + Url::fromPath('monitoring/list/hosts')->setQueryString($filterString) + )), + new HtmlElement('li', null, new Link( + 'monitoring/list/services', + Url::fromPath('monitoring/list/services')->setQueryString($filterString) + )), + new HtmlElement('li', null, new Link( + 'monitoring/list/hostgroups', + Url::fromPath('monitoring/list/hostgroups')->setQueryString($filterString) + )), + new HtmlElement('li', null, new Link( + 'monitoring/list/servicegroups', + Url::fromPath('monitoring/list/servicegroups')->setQueryString($filterString) + )) + ); + + break; + case 'application/share/users': + $filter = Filter::any(); + foreach ($restrictions as $roleRestriction) { + $userNames = StringHelper::trimSplit($roleRestriction); + foreach ($userNames as $userName) { + $filter->add(Filter::equal('user_name', $userName)); + } + } + + $filterString = QueryString::render($filter); + $list = new HtmlElement( + 'ul', + Attributes::create(['class' => 'links']), + new HtmlElement('li', null, new Link( + 'user/list', + Url::fromPath('user/list')->setQueryString($filterString) + )) + ); + + break; + case 'application/share/groups': + $filter = Filter::any(); + foreach ($restrictions as $roleRestriction) { + $groupNames = StringHelper::trimSplit($roleRestriction); + foreach ($groupNames as $groupName) { + $filter->add(Filter::equal('group_name', $groupName)); + } + } + + $filterString = QueryString::render($filter); + $list = new HtmlElement( + 'ul', + Attributes::create(['class' => 'links']), + new HtmlElement('li', null, new Link( + 'group/list', + Url::fromPath('group/list')->setQueryString($filterString) + )) + ); + + break; + default: + $filterString = join(', ', $restrictions); + $list = null; + } + + return [$filterString, $list]; + } +} diff --git a/library/Icinga/Web/View/helpers/format.php b/library/Icinga/Web/View/helpers/format.php new file mode 100644 index 0000000..4008583 --- /dev/null +++ b/library/Icinga/Web/View/helpers/format.php @@ -0,0 +1,72 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Web\View; + +use Icinga\Date\DateFormatter; +use Icinga\Util\Format; + +$this->addHelperFunction('format', function () { + return Format::getInstance(); +}); + +$this->addHelperFunction('formatDate', function ($date) { + if (! $date) { + return ''; + } + return DateFormatter::formatDate($date); +}); + +$this->addHelperFunction('formatDateTime', function ($dateTime) { + if (! $dateTime) { + return ''; + } + return DateFormatter::formatDateTime($dateTime); +}); + +$this->addHelperFunction('formatDuration', function ($seconds) { + if (! $seconds) { + return ''; + } + return DateFormatter::formatDuration($seconds); +}); + +$this->addHelperFunction('formatTime', function ($time) { + if (! $time) { + return ''; + } + return DateFormatter::formatTime($time); +}); + +$this->addHelperFunction('timeAgo', function ($time, $timeOnly = false, $requireTime = false) { + if (! $time) { + return ''; + } + return sprintf( + '<span class="relative-time time-ago" title="%s">%s</span>', + DateFormatter::formatDateTime($time), + DateFormatter::timeAgo($time, $timeOnly, $requireTime) + ); +}); + +$this->addHelperFunction('timeSince', function ($time, $timeOnly = false, $requireTime = false) { + if (! $time) { + return ''; + } + return sprintf( + '<span class="relative-time time-since" title="%s">%s</span>', + DateFormatter::formatDateTime($time), + DateFormatter::timeSince($time, $timeOnly, $requireTime) + ); +}); + +$this->addHelperFunction('timeUntil', function ($time, $timeOnly = false, $requireTime = false) { + if (! $time) { + return ''; + } + return sprintf( + '<span class="relative-time time-until" title="%s">%s</span>', + DateFormatter::formatDateTime($time), + DateFormatter::timeUntil($time, $timeOnly, $requireTime) + ); +}); diff --git a/library/Icinga/Web/View/helpers/generic.php b/library/Icinga/Web/View/helpers/generic.php new file mode 100644 index 0000000..bfd3f86 --- /dev/null +++ b/library/Icinga/Web/View/helpers/generic.php @@ -0,0 +1,15 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Web\View; + +use Icinga\Authentication\Auth; +use Icinga\Web\Widget; + +$this->addHelperFunction('auth', function () { + return Auth::getInstance(); +}); + +$this->addHelperFunction('widget', function ($name, $options = null) { + return Widget::create($name, $options); +}); diff --git a/library/Icinga/Web/View/helpers/string.php b/library/Icinga/Web/View/helpers/string.php new file mode 100644 index 0000000..b3f667b --- /dev/null +++ b/library/Icinga/Web/View/helpers/string.php @@ -0,0 +1,36 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Web\View; + +use Icinga\Util\StringHelper; +use Icinga\Web\Helper\Markdown; + +$this->addHelperFunction('ellipsis', function ($string, $maxLength, $ellipsis = '...') { + return StringHelper::ellipsis($string, $maxLength, $ellipsis); +}); + +$this->addHelperFunction('nl2br', function ($string) { + return nl2br(str_replace(array('\r\n', '\r', '\n'), '<br>', $string), false); +}); + +$this->addHelperFunction('markdown', function ($content, $containerAttribs = null) { + if (! isset($containerAttribs['class'])) { + $containerAttribs['class'] = 'markdown'; + } else { + $containerAttribs['class'] .= ' markdown'; + } + + return '<section' . $this->propertiesToString($containerAttribs) . '>' . Markdown::text($content) . '</section>'; +}); + +$this->addHelperFunction('markdownLine', function ($content, $containerAttribs = null) { + if (! isset($containerAttribs['class'])) { + $containerAttribs['class'] = 'markdown inline'; + } else { + $containerAttribs['class'] .= ' markdown inline'; + } + + return '<section' . $this->propertiesToString($containerAttribs) . '>' . + Markdown::line($content) . '</section>'; +}); diff --git a/library/Icinga/Web/View/helpers/url.php b/library/Icinga/Web/View/helpers/url.php new file mode 100644 index 0000000..277c237 --- /dev/null +++ b/library/Icinga/Web/View/helpers/url.php @@ -0,0 +1,158 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Web\View; + +use Icinga\Web\Url; +use Icinga\Exception\ProgrammingError; + +$view = $this; + +$this->addHelperFunction('href', function ($path = null, $params = null) use ($view) { + return $view->url($path, $params); +}); + +$this->addHelperFunction('url', function ($path = null, $params = null) { + if ($path === null) { + $url = Url::fromRequest(); + } elseif ($path instanceof Url) { + $url = $path; + } else { + $url = Url::fromPath($path); + } + + if ($params !== null) { + if ($url === $path) { + $url = clone $url; + } + + $url->overwriteParams($params); + } + + return $url; +}); + +$this->addHelperFunction( + 'qlink', + function ($title, $url, $params = null, $properties = null, $escape = true) use ($view) { + $icon = ''; + if ($properties) { + if (array_key_exists('title', $properties) && !array_key_exists('aria-label', $properties)) { + $properties['aria-label'] = $properties['title']; + } + + if (array_key_exists('icon', $properties)) { + $icon = $view->icon($properties['icon']); + unset($properties['icon']); + } + + if (array_key_exists('img', $properties)) { + $icon = $view->img($properties['img']); + unset($properties['img']); + } + } + + return sprintf( + '<a href="%s"%s>%s</a>', + $view->url($url, $params), + $view->propertiesToString($properties), + $icon . ($escape ? $view->escape($title) : $title) + ); + } +); + +$this->addHelperFunction('img', function ($url, $params = null, array $properties = array()) use ($view) { + if (! array_key_exists('alt', $properties)) { + $properties['alt'] = ''; + } + + $ariaHidden = array_key_exists('aria-hidden', $properties) ? $properties['aria-hidden'] : null; + if (array_key_exists('title', $properties)) { + if (! array_key_exists('aria-label', $properties) && $ariaHidden !== 'true') { + $properties['aria-label'] = $properties['title']; + } + } elseif ($ariaHidden === null) { + $properties['aria-hidden'] = 'true'; + } + + return sprintf( + '<img src="%s"%s />', + $view->escape($view->url($url, $params)->getAbsoluteUrl()), + $view->propertiesToString($properties) + ); +}); + +$this->addHelperFunction('icon', function ($img, $title = null, array $properties = array()) use ($view) { + if (strpos($img, '.') !== false) { + if (array_key_exists('class', $properties)) { + $properties['class'] .= ' icon'; + } else { + $properties['class'] = 'icon'; + } + if (strpos($img, '/') === false) { + return $view->img('img/icons/' . $img, null, $properties); + } else { + return $view->img($img, null, $properties); + } + } + + $ariaHidden = array_key_exists('aria-hidden', $properties) ? $properties['aria-hidden'] : null; + if ($title !== null) { + $properties['role'] = 'img'; + $properties['title'] = $title; + + if (! array_key_exists('aria-label', $properties) && $ariaHidden !== 'true') { + $properties['aria-label'] = $title; + } + } elseif ($ariaHidden === null) { + $properties['aria-hidden'] = 'true'; + } + + if (isset($properties['class'])) { + $properties['class'] .= ' icon-' . $img; + } else { + $properties['class'] = 'icon-' . $img; + } + + return sprintf('<i %s></i>', $view->propertiesToString($properties)); +}); + +$this->addHelperFunction('propertiesToString', function ($properties) use ($view) { + if (empty($properties)) { + return ''; + } + $attributes = array(); + + foreach ($properties as $key => $val) { + if ($key === 'style' && is_array($val)) { + if (empty($val)) { + continue; + } + $parts = array(); + foreach ($val as $k => $v) { + $parts[] = "$k: $v"; + } + $val = implode('; ', $parts); + continue; + } + + $attributes[] = $view->attributeToString($key, $val); + } + return ' ' . implode(' ', $attributes); +}); + +$this->addHelperFunction('attributeToString', function ($key, $value) use ($view) { + // TODO: Doublecheck this! + if (! preg_match('~^[a-zA-Z0-9-]+$~', $key)) { + throw new ProgrammingError( + 'Trying to set an invalid HTML attribute name: %s', + $key + ); + } + + return sprintf( + '%s="%s"', + $key, + $view->escape($value) + ); +}); |