For the complete documentation index, see llms.txt. This page is also available as Markdown.

Search page

Status: 85% — form and result rows done, pagination remaining.

What it is

The core search page, search.php. It renders two distinct things through two separate tablerender() calls:

  • the search form — caption PAGE_NAME SITENAME, mode search_head

  • one result block per search area — mode search_result, called once for every plugin that answered the query

With All Areas selected the visitor therefore sees several stacked cards, one per area, each with its own caption.

Where the template comes from

search.php loads the form template on construction:

$this->template = $this->formTemplate(e107::getCoreTemplate('search'));

coreTemplatePath() resolves the file in this order:

  1. THEME/templates/search_template.php — the v2 theme override

  2. THEME/search_template.php — v1 legacy override

  3. e107_core/templates/legacy/search_template.php — only if THEME_LEGACY

  4. e107_core/templates/search_template.php — the default

The form key is replaced, not merged

getCoreTemplate() merges the core template with the theme override, but the merge is array_merge() on top-level keys only. All seven form variants live under the single form key:

Shortcodes available in the form

The shortcode object is search.php's own class, passed directly to parseTemplate():

Shortcode
Renders

{SEARCH_FORM_URL}

form action

{SEARCH_MAIN}

complete input group — input, submit, advanced dropdown

{SEARCH_MAIN_SEARCHFIELD}

the bare <input> only

{SEARCH_MAIN_SUBMIT}

submit button plus the hidden r field

{SEARCH_ENHANCED}

the four enhanced fields, one per form.enhanced

{ENHANCED_DISPLAY}

style="display:none" unless enhanced is active

{SEARCH_MAIN_CHECKBOXES} / {SEARCH_DROPDOWN}

area selector, depending on the selector pref

{SEARCH_ADVANCED}

per-area advanced block

{SEARCH_MESSAGE}

stopword notice

form.enhanced and form.advanced are not parsed for shortcodes — they go through simpleParse(), so only the {ENHANCED_*} and {SEARCH_ADV_*} placeholders work there.

The search field: why not {SEARCH_MAIN}

{SEARCH_MAIN} emits its own input-group, its own btn-primary buttons and a Font Awesome fa-search glyph, none of which can be parameterized. Aragorn builds the input group in the template instead and uses only the bare field:

The dropdown toggle needs no <span class="caret">; Bootstrap 5 draws the arrow with .dropdown-toggle::after. Only data-bs-toggle is used, since Aragorn ships tabler.min.js (Bootstrap 5.3.7) and has no BS4 alias layer.

Horizontal rows for the secondary fields

The core template intended col-sm-3 / col-sm-9 rows but never wrapped them in .row, so the columns never formed one. Aragorn restores the intent and swaps the Bootstrap 3 control-label for col-form-label, which matches the label's vertical padding to the input height:

col-sm-4 rather than col-sm-3 — at 25% the label Word(s) beginning with wraps to two lines inside the main column of the 5-14-5 grid. Below sm the row collapses and the fields stack, which is the wanted mobile behaviour.

The main search field stays full width: it is the primary control and should dominate; the horizontal rows visually mark the rest as secondary.

Solved along the way: .form-control[size] broke the input group

The buttons kept wrapping onto a second line below the input. The cause was a rule in the theme's own style.css:

Specificity is 0,2,0 — one class plus one attribute — exactly the same as Tabler's .input-group > .form-control { flex: 1 1 auto; width: 1%; min-width: 0 }. On a tie the later stylesheet wins, and style.css loads last. The input then claimed the full container width and flex-wrap: wrap pushed the buttons down.

The rule was also redundant: Tabler's base .form-control already sets width: 100%, and an HTML size attribute is only a presentational hint that any CSS width overrides. Deleting it fixed the form.

The general lesson: e107 emits size= on many inputs, which tempts you to neutralise it in CSS. Don't — the framework already handles it, and the attribute selector quietly outranks the framework's own layout rules.

Rendering: the card_header style

Both search modes map to one reusable style in theme.php:

card_header renders a Tabler card with a .card-header, unlike the default case which prints a bare .card-body. It is also reachable from any layout via {SETSTYLE=card_header}.

The caption is assembled by core:

giving Results 1 - 10 of 42 in News. With no matches $res_display is empty, so it reads Results in News — awkward, and with a double space in the source, but it still identifies the area, which is the point.

Result rows are CSS-only

The form is fully templatable. The results are not — each row is built as a fixed string in search_class.php, wrapped in a list by search.php:

All three are direct children of the <li>, which lets the theme target them precisely without touching anything inside the summary:

Two notes on the details:

  • Tabler sizes h4 at 0.875rem for dashboard use. A result title needs to read as the primary element of its row, so the theme raises it to 1rem.

  • The date span is emitted inline and has to be promoted to display: block.

  • <mark> needs nothing — Tabler styles it through --tblr-highlight-bg / --tblr-highlight-color and ships separate dark-mode values.

There is no $this->bullet to account for: search_class.php picks one from GLYPH / BULLET / bullet2.gif and then unconditionally discards it two lines later with the comment "Use CSS instead".

Still open

Pagination. search.php wraps {NEXTPREV} in <div class="nextprev search form-inline"> — form-inline is a Bootstrap 3 / 4 class that no longer exists — and requests the default template prefix with a hardcoded value. Restyling it means overriding the default_* keys in THEME/templates/nextprev_template.php, which changes pagination site-wide, not just on search. Decision pending.

The navbar search box. $SEARCH_TEMPLATE['shortcode'] drives the {SEARCH} shortcode used in the header. It is a separate batch (e107_core/shortcodes/batch/search_shortcodes.php) whose {SEARCH_INPUT} and {SEARCH_BUTTON} do accept a class parm, so Tabler markup is straightforward there. Not yet written.

Security notes

  • Never interpolate $_GET['q'] into the template. The field value must come from {SEARCH_MAIN_SEARCHFIELD} (or {SEARCH_MAIN}), which runs it through $tp->post_toForm(). The same applies to the enhanced fields — sc_search_enhanced() escapes each value itself.

  • $mode reaches a class attribute. In tablestyle() the mode is used as a CSS class on the card. It comes from the tablerender() caller, which is plugin code rather than user input, but nothing guarantees a plugin did not build it from a request, so it is sanitised before use:

  • The caption needs no escaping. It is built from language constants and from search_info[$key]['qtype'], which each plugin declares in its e_search.php. The one path that touches $_GET uses the request value as an array key into a plugin-defined list, so no request data reaches the output. Escaping it with toHTML() would also break plugins that put legitimate markup in qtype.

Upstream findings

Working through this page surfaced eight issues in core search, from the missing Bootstrap 5 template to path constants leaking into result summaries and a mislabelled <label for> in the advanced form. They were written up as a single report for the e107 maintainers rather than filed as individual issues.

Last updated