Docs: Actions and sections

Actions and sections

Template API calls where core decides and the theme draws — action(), actions(), may(), section() and searchEnabled(), so a theme never hard-codes an address or a rule.

A theme decides how things look, never who may do what or where a module lives. These five calls hand the theme finished answers: a control core has already decided on, or the address a site gave a module. They arrived in Tilspire 1.18.0. Every example uses $esc, the shipped theme's escaper: static fn ($v): string => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8').

Where a module lives: section() and searchEnabled() #

section(string $id, string $path = '') #

Returns a module section's {url, title} at the address the site gave it, with $path appended, or null while the module is off or no module declares that section. Both values are escaped (E): echo them as they are.

<?php $blog = $this->section('blog'); ?>
<?php if ($blog !== null): ?><a class="more" href="<?= $blog['url'] ?>">All posts</a><?php endif; ?>

<?php $post = $this->section('blog', '/' . rawurlencode($slug)); ?>
  • section('forums', '/t/1-hello') returns ['url' => '/discuss/t/1-hello', 'title' => 'Community'] once the site has renamed Forums. $path is appended as given, with its own leading /.
  • The language prefix is added only for a route its module declared for languages (the Blog). Any other section has one address in every language.
  • The title is the site's own name for the section, or the module's translated default when none was set.
  • Test for null and leave the link out, so a site that turned the module off shows no dead link.

Never write /forums or /blog in a view. After a rename the link works only through a 301.

searchEnabled() #

Returns true while site search is on (bool). /search is routed only while search is on, so a form posting there on an install with search off lands on a 404. Draw a search box or link only when this is true.

<?php if ($this->searchEnabled()): ?>
<form action="<?= $esc($this->langUrl('/search')) ?>" method="get" role="search">…</form>
<?php endif; ?>

Controls core decides: action(), actions() and may() #

A module names its controls (delete a post, lock a topic, vote on a tracker item) and decides for each visitor whether to offer it, where it goes, what it says and whether it asks first. The theme places the result. The server still checks every action when it is used, so a control printed by hand gains nothing, and a control left out is only a missing button. Do not test isStaff(), a role or a permission to show or hide something.

action(string $name, array $item, array $opts = []) #

Returns the finished control as a string (E), or '' when the visitor is not offered it, the name is unknown, or the module failed (core logs it once and the page does not break). A POST control is a real <form> carrying the session's token, and is never offered to a guest.

<?= $this->action('forums.post.delete', $post, ['icon' => false]) ?>

actions(string $group, array $item, array $opts = []) #

Returns every control of a group that is offered to this visitor, in the module's order, joined with nothing between (E). Returns '' when none is offered or the group was never declared. $opts apply to each control.

<?= $this->actions('forums.post', $post, ['class' => 'btn btn-sm', 'tag' => 'li']) ?>

may(string $name, array $item) #

Returns true exactly when action() would print the control. Use it to wrap something of your own around a control. It is a yes or no for layout, never a rule.

<?php if ($this->may('forums.topic.lock', $topic)): ?>
  <div class="mod-bar"><?= $this->action('forums.topic.lock', $topic) ?></div>
<?php endif; ?>

Options #

All keys of $opts are optional. Any other key is refused and logged.

  • class — extra class names on the button or link.
  • tag — wraps the control in li, span or div.
  • icon — another Font Awesome solid name, or false for none.
  • label — another language key.
  • confirm — another language key, or false to drop the confirmation.
  • attrs — extra data-* or aria-* attributes. The action's own data-* win a clash.

Changing the markup #

Add a file; core uses the first it finds, in this order:

  1. your theme's components/actions/<name>.php
  2. your theme's components/action.php
  3. a module's components/actions/<name>.php
  4. core's components/action.php

The file receives $action with name, method (link or post), url, label, confirm, tone (danger or empty), icon (the class string), class, tag, hidden (a POST's token and fields) and attrs. All are already escaped: print them as they are. Keep a POST a form and never turn it into a link. A control with confirm asks through core/assets/theme-actions.js, which core loads for you.

Names shipped with 1.18.0 #

  • Forums — group forums.post (forums.post.quote, .permalink, .edit, .delete, .restore, .purge); group forums.topic.mod (forums.topic.pin, .unpin, .lock, .unlock, .rename, .move, .hide, .restore, .purge); and forums.topic.delete and forums.topic.docs_draft.
  • Comments — group comments.comment (comments.comment.edit, .delete).
  • Feedback's tracker — group tracker.item (tracker.item.vote and .unvote, both POST, and .edit, staff's link to the panel) on an item from trackerItems() or trackerItem(); and tracker.suggest on a trackers() entry or the list's tracker. Where one is not offered, print the item's vote_notice or the tracker's suggest_notice (language keys). See the Tracker page.

Maintenance mode #

With maintenance mode on, core answers a visitor whose role cannot enter the panel with its own page (503, Retry-After, noindex) before your templates run, so a theme writes no gate. Staff get the site: draw a banner when site()['maintenance'] is true.