diff --git a/app/controllers/admin/courses.php b/app/controllers/admin/courses.php index d9eced342b0bdf0386a0a7dd10620d4618fce741..519397d036d6d6c59e07fbbf2ffbb74d18d66b00 100644 --- a/app/controllers/admin/courses.php +++ b/app/controllers/admin/courses.php @@ -139,13 +139,33 @@ class Admin_CoursesController extends AuthenticatedController /* Order of elements: * Navigation + * actions * selected filters (configurable) * selected actions widget - * actions * view filter (configurable) * export */ + if ($GLOBALS['perm']->have_perm($this->sem_create_perm)) { + $actions = new ActionsWidget(); + $actions->addLink( + _('Neue Veranstaltung anlegen'), + URLHelper::getURL('dispatch.php/course/wizard'), + Icon::create('add') + )->asDialog('size=50%'); + $actions->addLink( + _('Diese Seitenleiste konfigurieren'), + URLHelper::getURL('dispatch.php/admin/courses/sidebar'), + Icon::create('admin') + )->asDialog(); + + + $sidebar->addWidget($actions, 'links'); + } + + $filter = new OptionsWidget(_('Filter')); + $filter->setId('admin-filter-widget'); + /* Now draw the configurable elements according to the values inside the visibleElements array. @@ -154,21 +174,23 @@ class Admin_CoursesController extends AuthenticatedController $this->setSearchWiget(); } if (!empty($visibleElements['institute'])) { - $this->setInstSelector(); + $filter->addElement($this->getInstSelector()); } if (!empty($visibleElements['semester'])) { - $this->setSemesterSelector(); + $filter->addElement($this->getSemesterSelector()); } if (!empty($visibleElements['stgteil'])) { - Sidebar::Get()->addWidget($this->getStgteilSelector(), 'filter_stgteil'); + $filter->addElement($this->getStgteilSelector()); } if (!empty($visibleElements['courseType'])) { - $this->setCourseTypeWidget(); + $filter->addElement($this->getCourseTypeWidget()); } if (!empty($visibleElements['teacher'])) { - Sidebar::Get()->addWidget($this->getTeacherWidget(), 'filter_teacher'); + $filter->addElement($this->getTeacherWidget()); } + $sidebar->addWidget($filter, 'filter'); + //if there are datafields in the list, draw their input fields, too: if (!empty($visibleElements['datafields'])) { //The datafields entry contains an array with datafield-IDs. @@ -192,34 +214,9 @@ class Admin_CoursesController extends AuthenticatedController } } - //this shall be visible in every case: $this->setActionsWidget(); - - //actions: always visible, too - if ($GLOBALS['perm']->have_perm($this->sem_create_perm)) { - $actions = new ActionsWidget(); - $actions->addLink( - _('Neue Veranstaltung anlegen'), - URLHelper::getURL('dispatch.php/course/wizard'), - Icon::create('add') - )->asDialog('size=50%'); - $actions->addLink( - _('Diese Seitenleiste konfigurieren'), - URLHelper::getURL('dispatch.php/admin/courses/sidebar'), - Icon::create('admin') - )->asDialog(); - - - $sidebar->addWidget($actions, 'links'); - } - - //the view filter's visibility is configurable: - if (in_array('viewFilter', $visibleElements)) { - $this->setViewWidget($this->view_filter); - } - //"export as Excel" is always visible: if ($this->sem_create_perm) { $params = []; @@ -807,7 +804,6 @@ class Admin_CoursesController extends AuthenticatedController $stgteilActive = Request::get('stgteilActive'); $courseTypeActive = Request::get('courseTypeActive'); $teacherActive = Request::get('teacherActive'); - $viewFilterActive = Request::get('viewFilterActive'); $activeDatafields = Request::getArray('activeDatafields'); /* @@ -833,9 +829,6 @@ class Admin_CoursesController extends AuthenticatedController if ($teacherActive) { $activeArray['teacher'] = true; } - if ($viewFilterActive) { - $activeArray['viewFilter'] = true; - } if ($activeDatafields) { $activeArray['datafields'] = $activeDatafields; @@ -862,13 +855,13 @@ class Admin_CoursesController extends AuthenticatedController public function get_stdgangteil_selector_action($institut_id) { $selector = $this->getStgteilSelector($institut_id); - $this->render_text($selector->render(['base_class' => 'sidebar'])); + $this->render_text($selector->render()); } public function get_teacher_selector_action($institut_id) { $selector = $this->getTeacherWidget($institut_id); - $this->render_text($selector->render(['base_class' => 'sidebar'])); + $this->render_text($selector->render()); } @@ -1594,61 +1587,27 @@ class Admin_CoursesController extends AuthenticatedController }); } - - /** - * Adds view filter to the sidebar - * @param array $configs - */ - private function setViewWidget() - { - $configs = $this->getFilterConfig(); - $checkbox_widget = new OptionsWidget(); - $checkbox_widget->setTitle(_('Darstellungsfilter')); - - foreach ($this->getViewFilters() as $index => $label) { - $state = in_array($index, $configs); - $checkbox_widget->addCheckbox( - $label, - $state, - $this->url_for('admin/courses/set_view_filter/' . $index . '/' . $state), - null, - ['onclick' => "$(this).toggleClass(['options-checked', 'options-unchecked']); $(this).attr('aria-checked', $(this).hasClass('options-checked') ? 'true' : 'false'); STUDIP.AdminCourses.App.toggleActiveField('".$index."'); return false;"] - ); - } - Sidebar::get()->addWidget($checkbox_widget, 'views'); - } - /** * Adds the institutes selector to the sidebar */ - private function setInstSelector() + private function getInstSelector() { - $sidebar = Sidebar::Get(); - $list = new SelectWidget( - _('Einrichtung'), - $this->url_for('admin/courses/set_selection'), - 'institute' - ); - $list->class = 'institute-list'; + $list = []; if ($GLOBALS['perm']->have_perm('root') || (count($this->insts) > 1)) { - $list->addElement(new SelectElement( + $list[] = new SelectElement( '', - $GLOBALS['perm']->have_perm('root') ? _('Alle') : _('Alle meine Einrichtungen'), - !$GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT), - 'select-all' + $GLOBALS['perm']->have_perm('root') ? _('Alle') : _('Alle meine Einrichtungen') ); } foreach ($this->insts as $institut) { - $list->addElement( + $list[] = new SelectElement( $institut['Institut_id'], (!$institut['is_fak'] ? ' ' : '') . $institut['Name'], $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT === $institut['Institut_id'] - ), - 'select-' . $institut['Institut_id'] - ); + ); //check if the institute is a faculty. //If true, then add another option to display all courses @@ -1656,66 +1615,62 @@ class Admin_CoursesController extends AuthenticatedController //$institut is an array, we can't use the method isFaculty() here! if ($institut['fakultaets_id'] === $institut['Institut_id']) { - $list->addElement( + $list[] = new SelectElement( $institut['Institut_id'] . '_withinst', //_withinst = with institutes ' ' . $institut['Name'] . ' +' . _('Institute'), ($GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT === $institut['Institut_id'] && $GLOBALS['user']->cfg->MY_INSTITUTES_INCLUDE_CHILDREN) - ), - 'select-' . $institut['Name'] . '-with_institutes' - ); + ); } } - $list->setOnSubmitHandler("STUDIP.AdminCourses.changeFiltersDependendOnInstitute($(this).find('select').val()); return false;"); - $sidebar->addWidget($list, 'filter_institute'); + return new SelectListElement(_('Einrichtung'), 'institute', $list, false, ['class' => 'institute-list', 'onchange' => "STUDIP.AdminCourses.changeFiltersDependendOnInstitute($(this).val()); return false;"], false); } /** * Adds the semester selector to the sidebar */ - private function setSemesterSelector() + private function getSemesterSelector() { $semesters = array_reverse(Semester::getAll()); - $sidebar = Sidebar::Get(); - $list = new SelectWidget(_('Semester'), $this->url_for('admin/courses/set_selection'), 'sem_select'); - $list->addElement(new SelectElement('', _('Alle')), 'sem_select-all'); + $list = []; + $list[] = new SelectElement('', _('Alle')); foreach ($semesters as $semester) { - $list->addElement(new SelectElement( + $list[] = new SelectElement( $semester->id, $semester->name, $semester->id === $GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE - ), 'sem_select-' . $semester->id); + ); } - $list->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({semester_id: $(this).find('select').val()}); return false;"); - $sidebar->addWidget($list, 'filter_semester'); + return new SelectListElement(_('Semester'), 'sem_select', $list, false, ['onchange' => "STUDIP.AdminCourses.App.changeFilter({semester_id: $(this).val()}); return false;"], false); } - /** + /** * Adds the studiengangteil selector to the sidebar */ private function getStgteilSelector($institut_id = null) { $institut_id = $institut_id ?: $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT; $stgteile = StudiengangTeil::getAllEnriched('fach_name', 'ASC', ['mvv_fach_inst.institut_id' => $institut_id]); - $list = new SelectWidget(_('Studiengangteil'), $this->url_for('admin/courses/set_selection'), 'stgteil_select'); + $list = []; if (!$institut_id || $institut_id === 'all') { - $list->addElement(new SelectElement('', _('Wählen Sie eine Einrichtung') ), 'stgteil_select-all'); - } elseif (count($stgteile) === 0) { - $list->addElement(new SelectElement('', _('Keine Studiengangteile zu der gewählten Einrichtung') ), 'stgteil_select-all'); + $list[] = new SelectElement('', _('Wählen Sie eine Einrichtung')); + } else if (count($stgteile) === 0) { + $list[] = new SelectElement('', _('Keine Studiengangteile zu der gewählten Einrichtung')); } else { - $list->addElement(new SelectElement('', _('Alle')), 'stgteil_select-all'); + $list[] = new SelectElement('', _('Alle')); } + foreach ($stgteile as $stgteil) { - $list->addElement(new SelectElement( + $list[] = new SelectElement( $stgteil->id, $stgteil->getDisplayName(), $stgteil->id === $GLOBALS['user']->cfg->MY_COURSES_SELECTED_STGTEIL - ), 'stgteil_select-' . $stgteil->id); + ); } - $list->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({stgteil: $(this).find('select').val()}); return false;"); - return $list; + + return new SelectListElement(_('Studiengangteil'), 'stgteil_select', $list, false, ['onchange' => "STUDIP.AdminCourses.App.changeFilter({stgteil: $(this).val()}); return false;"], false); } @@ -1747,21 +1702,12 @@ class Admin_CoursesController extends AuthenticatedController * @param string $selected * @param array $params */ - private function setCourseTypeWidget() + private function getCourseTypeWidget() { - $sidebar = Sidebar::get(); - $this->url = $this->url_for('admin/courses/set_course_type'); - $this->types = []; - $this->selected = $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER; - - $list = new SelectWidget( - _('Veranstaltungstypfilter'), - $this->url_for('admin/courses/set_course_type'), - 'course_type' + $list = []; + $list[] = new SelectElement( + '', _('Alle') ); - $list->addElement(new SelectElement( - '', _('Alle'), !$GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER - ), 'course-type-all'); foreach ($GLOBALS['SEM_CLASS'] as $class_id => $class) { if ($class['studygroup_mode']) { continue; @@ -1772,10 +1718,7 @@ class Admin_CoursesController extends AuthenticatedController $class['name'], $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER === (string)$class_id ); - $list->addElement( - $element->setAsHeader(), - 'course-type-' . $class_id - ); + $list[] = $element->setAsHeader(); foreach ($class->getSemTypes() as $id => $result) { $element = new SelectElement( @@ -1783,14 +1726,11 @@ class Admin_CoursesController extends AuthenticatedController $result['name'], $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER === $class_id . '_' . $id ); - $list->addElement( - $element->setIndentLevel(1), - 'course-type-' . $class_id . '_' . $id - ); + $list[] = $element->setIndentLevel(1); } } - $list->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({course_type: $(this).find('select').val()}); return false;"); - $sidebar->addWidget($list, 'filter-course-type'); + + return new SelectListElement(_('Veranstaltungstypfilter'), 'course_type', $list, false, ['onchange' => "STUDIP.AdminCourses.App.changeFilter({course_type: $(this).val()}); return false;"], false); } /** @@ -1821,24 +1761,24 @@ class Admin_CoursesController extends AuthenticatedController ); - $list = new SelectWidget(_('Lehrendenfilter'), $this->url_for('admin/courses/index'), 'teacher_filter'); + $list = []; if (!$institut_id || $institut_id === 'all') { - $list->addElement(new SelectElement('', _('Wählen Sie eine Einrichtung') ), 'teacher_filter-all'); - } elseif (count($teachers) === 0) { - $list->addElement(new SelectElement('', _('Keine Lehrenden in der gewählten Einrichtung') ), 'teacher_filter-all'); + $list[] = new SelectElement('', _('Wählen Sie eine Einrichtung')); + } else if (count($teachers) === 0) { + $list[] = new SelectElement('', _('Keine Lehrenden in der gewählten Einrichtung')); } else { - $list->addElement(new SelectElement('', _('Alle')), 'teacher_filter-all'); + $list[] = new SelectElement('', _('Alle')); } foreach ($teachers as $teacher) { - $list->addElement(new SelectElement( + $list[] = new SelectElement( $teacher['user_id'], $teacher['fullname'], $GLOBALS['user']->cfg->ADMIN_COURSES_TEACHERFILTER === $teacher['user_id'] - ), 'teacher_filter-' . $teacher['user_id']); + ); } - $list->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({teacher_filter: $(this).find('select').val()}); return false;"); - return $list; + + return new SelectListElement(_('Lehrendenfilter'), 'teacher_filter', $list, false, ['onchange' => "STUDIP.AdminCourses.App.changeFilter({teacher_filter: $(this).val()}); return false;"], false); } /** @@ -1918,7 +1858,6 @@ class Admin_CoursesController extends AuthenticatedController 'stgteil' => true, 'courseType' => true, 'teacher' => true, - 'viewFilter' => true ]; } diff --git a/app/views/admin/courses/sidebar.php b/app/views/admin/courses/sidebar.php index 6b243caa42806b51258de36015185f934ba7c534..f1a2dcf208bd9bdeda130d94e729fc04f07bce71 100644 --- a/app/views/admin/courses/sidebar.php +++ b/app/views/admin/courses/sidebar.php @@ -46,12 +46,6 @@ > <?= _('Veranstaltungstypfilter'); ?> </label> - <label> - <input name="viewFilterActive" type="checkbox" value="1" - <?= (!empty($userSelectedElements['viewFilter'])) ? 'checked' : '' ?> - > - <?= _('Darstellungsfilter'); ?> - </label> </fieldset> <? if ($datafields): ?> <fieldset> diff --git a/lib/classes/ActionMenu.php b/lib/classes/ActionMenu.php index 07b87a914e2128f00d4b6cee99697b2df9d74a90..cf4fb80cd4fc5c4fe03f66fd4fe804fe1e5a2298 100644 --- a/lib/classes/ActionMenu.php +++ b/lib/classes/ActionMenu.php @@ -26,6 +26,7 @@ class ActionMenu private $actions = []; private $attributes = []; + private $title; private $condition_all = null; private $condition = true; @@ -48,6 +49,7 @@ class ActionMenu private function __construct() { $this->addCSSClass('action-menu'); + $this->setTitle(_('Aktionen')); } /** @@ -294,6 +296,7 @@ class ActionMenu } return $action; }, $this->actions); + $template->title = $this->title; $template->action_menu_title = $this->generateTitle(); $template->attributes = $this->attributes; return $template->render(); @@ -376,6 +379,20 @@ class ActionMenu return $rendering_mode; } + /** + * Sets the title for the action menu. + * + * @param string $title display title + * + * @return ActionMenu instance to allow chaining + */ + public function setTitle(string $title): ActionMenu + { + $this->title = $title; + + return $this; + } + /** * Generates the title of the action menu, including its context, if the context has been set. * @@ -385,11 +402,12 @@ class ActionMenu { if ($this->context) { return sprintf( - _('Aktionsmenü für %s'), + _('%s für %s'), + $this->title, $this->context ); } else { - return _('Aktionsmenü'); + return $this->title; } } } diff --git a/lib/classes/sidebar/SelectListElement.php b/lib/classes/sidebar/SelectListElement.php index 9cbb42c4ef2c6276bb00429ec1a6e5e33408e396..b8aee53f4f5c92942338e7bcab0db96b6071bc00 100644 --- a/lib/classes/sidebar/SelectListElement.php +++ b/lib/classes/sidebar/SelectListElement.php @@ -7,19 +7,22 @@ class SelectListElement extends WidgetElement implements ArrayAccess protected $name; protected $options; protected $selected_option; + protected $submit_upon_select; public function __construct( string $label, string $name, array $options, $selected_option = null, - array $attributes = [] + array $attributes = [], + bool $submit_upon_select = true ) { $this->label = $label; $this->name = $name; $this->options = $options; $this->selected_option = $selected_option; $this->attributes = $attributes; + $this->submit_upon_select = $submit_upon_select; } public function setOptions(array $options): void @@ -30,24 +33,41 @@ class SelectListElement extends WidgetElement implements ArrayAccess public function render() { $option_content = ''; + $nested_select = ''; + $submit_upon_select = $this->submit_upon_select ? ' submit-upon-select' : ''; foreach ($this->options as $value => $option) { - $selected = $value == $this->selected_option ? 'selected' : ''; + if ($option instanceof SelectElement) { + if ($option->isHeader() || $option->getIndentLevel() > 0) { + $nested_select = ' nested-select'; + } + $option_attr = [ + 'value' => $option->getId(), + 'class' => ($option->isHeader() ? 'nested-item-header' : '') . ($option->getIndentLevel() ? ' nested-item' : ''), + 'title' => $option->getTooltip() ?: $option->getLabel(), + 'selected' => $option->isActive() + ]; + $option_label = $option->getLabel(); + } else { + $option_attr = compact('value'); + $option_label = $option; + } + $option_content .= sprintf( - '<option value="%s" %s>%s</option>', - htmlReady($value), - $selected, - htmlReady($option) + '<option %s>%s</option>', + arrayToHtmlAttributes($option_attr), + htmlReady($option_label) ); } $attributes = $this->attributes; - $attributes['class'] = trim(($attributes['class'] ?? '') . ' sidebar-selectlist submit-upon-select'); + $attributes['class'] = trim(($attributes['class'] ?? '') . ' sidebar-selectlist' . $submit_upon_select . $nested_select); $attributes['name'] = $this->name; $attributes['aria-label'] = $this->label; return sprintf( - '<select %s>%s</select>', + '<label><div class="label-text" hidden>%s</div><select %s>%s</select></label>', + htmlReady($this->label), arrayToHtmlAttributes($attributes), $option_content ); diff --git a/resources/assets/javascripts/lib/admin-courses.js b/resources/assets/javascripts/lib/admin-courses.js index e6a3523db54835a9fbf559c2c80126b4049cc52a..23cf6dd3f337a10ded198baab6a2b8efae1db25e 100644 --- a/resources/assets/javascripts/lib/admin-courses.js +++ b/resources/assets/javascripts/lib/admin-courses.js @@ -6,14 +6,14 @@ const AdminCourses = { $.get( STUDIP.URLHelper.getURL('dispatch.php/admin/courses/get_stdgangteil_selector/' + institut_id) ).done((widget) => { - $('select[name=stgteil_select]').closest('.sidebar-widget').replaceWith(widget); + $('select[name=stgteil_select]').closest('label').replaceWith(widget); }); //change Dozenten-Filter $.get( STUDIP.URLHelper.getURL('dispatch.php/admin/courses/get_teacher_selector/' + institut_id) ).done((widget) => { - $('select[name=teacher_filter]').closest('.sidebar-widget').replaceWith(widget); + $('select[name=teacher_filter]').closest('label').replaceWith(widget); }); } }; diff --git a/resources/assets/stylesheets/scss/admin-courses.scss b/resources/assets/stylesheets/scss/admin-courses.scss index bbc06b03c85864bbd9b6dcaee241a131788bcf44..8b8b736bd1a9786f9f9b0bfdb18c82cfd39c1ab1 100644 --- a/resources/assets/stylesheets/scss/admin-courses.scss +++ b/resources/assets/stylesheets/scss/admin-courses.scss @@ -28,3 +28,7 @@ content: url("#{$image-path}/icons/white/file.svg"); } } + +#admin-filter-widget .label-text { + display: block; +} diff --git a/resources/vue/components/AdminCourses.vue b/resources/vue/components/AdminCourses.vue index 0f79cf7febf0ab9c7ff9378d3afbff238b9394ed..b27e6f6c6e6bb6c681eea0191cbec0b6e49785bb 100644 --- a/resources/vue/components/AdminCourses.vue +++ b/resources/vue/components/AdminCourses.vue @@ -38,8 +38,9 @@ {{ fields[activeField] }} </template> </th> - <th class="actions" style="text-align: center;"> + <th class="actions"> {{ $gettext('Aktion') }} + <studip-action-menu :title="$gettext('Darstellungsfilter')" :items="availableFields" @toggleActiveField="toggleActiveField"></studip-action-menu> </th> </tr> <tr v-if="buttons.top"> @@ -176,6 +177,17 @@ export default { sortedActivatedFields() { return Object.keys(this.fields).filter(f => this.activatedFields.includes(f)); }, + availableFields() { + return Object.keys(this.fields).map(f => { + let state = this.activatedFields.includes(f); + return { + label: this.fields[f], + icon: state ? 'checkbox-checked' : 'checkbox-unchecked', + emit: 'toggleActiveField', + emitArguments: f, + } + }); + }, loadingIndicator() { return STUDIP.ASSETS_URL + 'images/loading-indicator.svg'; } diff --git a/resources/vue/components/StudipActionMenu.vue b/resources/vue/components/StudipActionMenu.vue index 014a1cacefc1857f4381e6bd5da019c9041039f6..c70d8edd55e170ebbd1af7dd08311126bfeceb85 100644 --- a/resources/vue/components/StudipActionMenu.vue +++ b/resources/vue/components/StudipActionMenu.vue @@ -1,13 +1,13 @@ <template> <div v-if="shouldCollapse" class="action-menu"> - <button class="action-menu-icon" :title="title" aria-expanded="false"> + <button class="action-menu-icon" :title="tooltip" aria-expanded="false"> <span></span> <span></span> <span></span> </button> <div class="action-menu-content"> <div class="action-menu-title"> - {{ $gettext('Aktionen') }} + {{ title }} </div> <ul class="action-menu-list"> <li v-for="item in navigationItems" :key="item.id" class="action-menu-item"> @@ -50,6 +50,12 @@ export default { context: { type: String, default: '' + }, + title: { + type: String, + default() { + return this.$gettext('Aktionen'); + } } }, data () { @@ -122,8 +128,8 @@ export default { } return Number.parseInt(collapseAt) <= this.items.filter((item) => item.type !== 'separator').length; }, - title () { - return this.context ? this.$gettextInterpolate(this.$gettext('Aktionsmenü für %{context}'), {context: this.context}) : this.$gettext('Aktionsmenü'); + tooltip () { + return this.context ? this.$gettextInterpolate(this.$gettext('%{title} für %{context}'), {title: this.title, context: this.context}) : this.title; } } } diff --git a/templates/shared/action-menu.php b/templates/shared/action-menu.php index 4f3b5f10ff9e09ba635bc12b5f6df2e723eb351e..bc2cc5045284cbe6d03804514673b7ba49288d3d 100644 --- a/templates/shared/action-menu.php +++ b/templates/shared/action-menu.php @@ -20,9 +20,9 @@ </button> <div class="action-menu-content"> <div class="action-menu-title" aria-hidden="true"> - <?= _('Aktionen') ?> + <?= htmlReady($title) ?> </div> - <ul class="action-menu-list" aria-label="<?= _('Aktionen') ?>"> + <ul class="action-menu-list" aria-label="<?= htmlReady($title) ?>"> <? foreach ($actions as $action): ?> <li class="action-menu-item <? if (isset($action['attributes']['disabled'])) echo 'action-menu-item-disabled'; ?>"> <? if ($action['type'] === 'link'): ?>