An application whose listeners.json or extensions.json does not parse installs perfectly, shows its AdminCP screens, saves its settings — and never runs a single line of its own automation. There is no error, no log entry, and nothing in the interface that looks wrong.
The cause
Core reads both manifests the same way, inside an and chain, with the warning suppressed:
if ( file_exists( $jsonFile ) and $json = @json_decode( file_get_contents( $jsonFile ), TRUE ) )
{
foreach( $json as $filename => $data )
{
// ... register
}
}
A malformed file makes json_decode() return NULL. The and short-circuits, the loop never runs, and the method returns an empty array. The @ hides the only clue there would otherwise be.
The application is still enabled. Its tables exist. Its ACP module works. It simply contributes no listeners and no extensions.
The usual cause: a single backslash
These manifests are full of namespaced class names, and JSON has no \e, \M or \n-that-you-meant escape.
// INVALID - \e and \l and \M are not JSON escapes "classname": "IPS\myapp\listeners\MemberEvents" // CORRECT "classname": "IPS\\myapp\\listeners\\MemberEvents"
Both look reasonable in an editor. Only one parses.
Worse, a single backslash before certain letters produces a valid escape that silently corrupts the value rather than failing outright. "IPS\nexus\\Invoice\\Item" parses cleanly and yields a string containing a literal newline followed by exus\Invoice\Item. The manifest loads, the class name is nonsense, and class_exists() quietly returns false.
The second silent failure: a class that is not there
Even with valid JSON, core checks before registering:
if( file_exists( $directory . '/' . $filename . '.php' ) )
{
if( class_exists( $data['classname'] ) )
{
$listeners[] = $data['classname'];
}
}
Declare something you never wrote — easy to do when a manifest is created during scaffolding and the class comes later, or never — and it is skipped without a word.
How to detect it
Neither failure is visible at runtime, so check the files themselves. Two passes: does every manifest parse, and does every declared class have a file?
foreach ( glob( $root . '/*/data/*.json' ) as $file )
{
$raw = file_get_contents( $file );
if ( trim( $raw ) === '' ) { continue; }
json_decode( $raw, TRUE );
if ( json_last_error() !== JSON_ERROR_NONE )
{
echo 'BROKEN ' . $file . ' - ' . json_last_error_msg() . PHP_EOL;
}
}
Then, for each entry in listeners.json, confirm listeners/<key>.php exists; for each entry in extensions.json, confirm extensions/<app>/<type>/<key>.php exists. The key in the manifest is the filename.
Confirming it at runtime
If you suspect an application is contributing nothing, ask core directly rather than reading the file:
foreach ( \IPS\Application::enabledApplications() as $dir => $app )
{
foreach ( (array) $app->listeners() as $class )
{
echo $dir . ' :: ' . $class . PHP_EOL;
}
}
foreach ( \IPS\Application::allExtensions( 'core', 'Loader' ) as $key => $ext )
{
echo $key . PHP_EOL;
}
An application missing from that output has a manifest problem, whatever the file looks like. Clear the datastore first, since extension lists are cached.
Why this is worth a standing check
Every other mistake in an application announces itself somewhere — a fatal, a missing language string, a blank block. This one produces an application that is perfectly plausible and does nothing, and the natural response is to go and debug the code that was never invoked. Validating the manifests takes a second and rules it out for good.
Verified against
Invision Community 5.0.19, by reading Application::listeners() and Application::extensions() in system/Application/Application.php, and by shipping an application that had this exact fault.
Recommended Comments