Skip to content
View in the app

A better way to browse. Learn more.

ernestdefoe.online

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
ernestdefoe.online

Extensions, themes & support for Flarum and Invision Community

Vibe coding for the community web. Report a bug, request a feature, or dig into the source — this is where the tools you use get built, in the open.

We do custom Bespoke Invision Community apps. If you have an idea for something you want then use the contact form to get in touch with us.
Knowledge base

Things that cost me a day, so they cost you none

Working notes from building Invision Community and Flarum applications. Mostly the failures that give no error at all — the ones where everything installs cleanly and quietly does the wrong thing.

92 articles

Invision Community 5

86 articles

Extensions and contracts

39

What each extension point is for, what it must declare, and what happens when it is wrong — which is usually nothing visible.

Languages and text

5

The string table, translation, and the places where text does not appear where you expected it to.

Theming, templates and forms

9

Theme hooks, CSS that survives both colour schemes, and building forms that do not throw on render.

Background work and scheduled tasks

5

The queue system, work that has to happen after the response, and jobs that finish without doing anything.

Data, settings and storage

11

The database layer, settings, tags, file storage, and backing up a live site.

AI features and expectations

5

What these features do, what they cost, and what buyers reasonably but wrongly assume they do.

Application structure and releases

11

The JSON files an application is made of, versioning and upgrade steps, and testing from the command line.

Realtime, chat and calls

1

WebSocket gateways, relays and the server-side pieces live features depend on — where "it works when I test it" and "it works for your members" are different claims.

Nothing matches that.

"This theme may be out of date" usually means your form field, not the theme

You add a field to an AdminCP form, load the page, and get this:

[[Template core/global/forms/checkboxset is throwing an error.
  This theme may be out of date. Run the support tool in the AdminCP
  to restore the default theme.]]

The theme is fine. Restoring it will not help. The message is what Invision Community prints when a template throws, and the template threw because of the options you passed to the field — but the message names the template, so it sends you to the support tool and the theme system instead of to the six lines you just wrote.

Why it points at the wrong place

Field construction does almost no validation. new CheckboxSet( ... ) stores your options and returns happily; the options are not read until the template renders them. So the stack you would want — "bad option in your controller" — does not exist by the time anything goes wrong. What is left is a template that could not cope with what it was handed.

Two consequences worth internalising: the error appears on page load rather than at the line you wrote, and it is worded as an infrastructure problem rather than a code one.

The usual cause: `unlimited` not matching the value

For a multi-select field with an "everything" option, three things have to agree. This is the shape core uses (see applications/core/modules/admin/applications/applications.php, the app_disabled_groups field):

$form->add( new CheckboxSet( 'my_groups', $allowed === '*' ? '*' : explode( ',', $allowed ), FALSE, array(
	'options'          => array_combine(
		array_keys( \IPS\Member\Group::groups() ),
		array_map( function ( $group ) { return (string) $group; }, \IPS\Member\Group::groups() )
	),
	'multiple'         => TRUE,
	'unlimited'        => '*',
	'unlimitedLang'    => 'my_groups_all',
	'impliedUnlimited' => TRUE,
) ) );

🚨 unlimited must be a scalar sentinel, and the field's value must be that same sentinel when everything is selected. Passing 'unlimited' => array() and a value of array() looks reasonable and produces the template error above.

🚨 options must be id => label. array_map() over Group::groups() looks like it builds that, but you lose the ids the moment the callback returns a string, so the checkboxes have nothing to submit. Hence the array_combine( array_keys( ... ), array_map( ... ) ) dance in core's own code — it is not superstition.

Then handle the sentinel on the way back out

With impliedUnlimited, the field returns the literal sentinel, not an array:

if ( $values['my_groups'] === '*' )
{
	$save = '*';
}
else
{
	$ids  = array_filter( array_map( 'intval', (array) $values['my_groups'] ) );
	$save = $ids ? implode( ',', $ids ) : '*';
}

🚨 Casting first is a real bug: (array) '*' is array('*'), which intval()s to 0 and gets filtered away, turning "everyone is allowed" into "nobody is allowed" — silently, and in the direction that hides things rather than exposing them.

Test it by rendering, not by constructing

Because construction does not validate, a test that builds the field proves nothing. Call html() and look for the marker:

$html = (string) $field->html();

$broken = str_contains( $html, 'is throwing an error' )
	or str_contains( $html, 'This theme may be out of date' );

Two things make this awkward from the command line, both avoidable:

  • 🚨 Do not boot Dispatcher\Admin::i() to "set up the ACP context". From CLI it tries to redirect to the login screen and dies on a missing REQUEST_METHOD. You do not need it: Theme::getTemplate() only consults the dispatcher when the location argument is omitted, and the form templates pass one.
  • Some fields — Node, and anything reaching Request->url() — read $_SERVER directly. Stub the handful they want rather than building a request:
    $_SERVER['REQUEST_METHOD'] ??= 'GET';
    $_SERVER['SERVER_NAME']    ??= 'localhost';
    $_SERVER['SERVER_PORT']    ??= '80';
    $_SERVER['HTTP_HOST']      ??= 'localhost';
    $_SERVER['REQUEST_URI']    ??= '/admin/';
    $_SERVER['SCRIPT_NAME']    ??= '/index.php';
    $_SERVER['QUERY_STRING']   ??= '';
    $_SERVER['HTTPS']          ??= '';

And assert that the broken version really is broken. A render check that passes whatever you feed it is worse than none, because it reads as coverage. Construct the field the wrong way on purpose, render it, and require that one to fail.

Verified against

Invision Community 5.0.19, against system/Helpers/Form/CheckboxSet.php, system/Theme/Dev/Theme.php and core's own app_disabled_groups and menu_manager_access fields.


User Feedback

Recommended Comments

There are no comments to display.

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.