The core/AdminNotifications extension is how something on the site gets an administrator's attention. It defines a type of ACP notification. Once one has been raised, the bell in the AdminCP header gains a count, the notification appears in the dropdown behind that bell and on the notification centre at app=core&module=overview&controller=notifications, and — if you declared it high or critical — it is also rendered as a coloured banner across the top of every AdminCP page. Critical ones go further still: they are drawn on the front end too, at the top of the main content area on every page, but only for members who are administrators. Each administrator can turn your notification type off, or ask to be emailed about it, from Notification Settings. This is the mechanism behind "Your licence expires soon", "New members are awaiting validation", "Tasks are not running" and Commerce's fraud-review queue.
The extension point is unusual in two ways. There is no AdminNotificationsAbstract under system/Extensions/, and the class you write is not a plain service object — it is an ActiveRecord. Each row in core_acp_notifications is an instance of your class.
Registration
Three things must line up.
- A file at
applications/{app}/extensions/core/AdminNotifications/{Key}.php. - An entry in
applications/{app}/data/extensions.jsonundercore→AdminNotifications→{Key}, pointing at the fully-qualified class name. Core's own file is a good reference:applications/core/data/extensions.json. - The class must extend
IPS\core\AdminNotification, which lives atapplications/core/sources/AdminNotification/AdminNotification.php— not insystem/Extensions/. The Developer Center stub isapplications/core/data/defaults/extensions/AdminNotifications.txt.
The namespace is not free-form. Core derives the per-admin preference key by exploding the class name on backslashes and taking elements 1 and 5 (AdminNotification.php lines 223–224, 371–372, and applications/core/modules/admin/overview/notifications.php lines 148–149):
$exploded = explode( '\\', get_class( $notification ) );
$key = "{$exploded[1]}_{$exploded[5]}";
That only produces the right answer for exactly IPS\{app}\extensions\core\AdminNotifications\{Key} — six segments. Any other depth is a silent misconfiguration; see the failure modes below.
The extension key is also passed through IPS::mb_ucfirst() before lookup (lines 280, 352 and 443), so the key in extensions.json must begin with an upper-case letter. A key of myThing can never be resolved.
The contract
Five methods are abstract and must be implemented. Everything else has a working default. Signatures below are taken character for character from applications/core/sources/AdminNotification/AdminNotification.php; the line number of each declaration is given.
abstract class AdminNotification extends ActiveRecord
{
/* --- REQUIRED --- */
/* 544. Language key used as the row label on the Notification Settings
matrix. Return the key, not the translated string. */
abstract public static function settingsTitle() : string;
/* 645. Full HTML. You are responsible for escaping. Rendered raw in the
popup, the notification centre, the ACP banner and the front-end
banner. */
abstract public function title() : string;
/* 662. Full HTML, or NULL. Rendered raw in the notification centre and
in the ACP/front-end banners. Not shown in the bell dropdown. */
abstract public function body() : ?string;
/* 669. MUST return one of the five SEVERITY_* constants. See below --
anything else is swallowed. */
abstract public function severity() : string;
/* 676. MUST return one of the four DISMISSIBLE_* constants. */
abstract public function dismissible() : string;
/* --- OPTIONAL, with defaults --- */
/* 516/521/526. Grouping and ordering on the settings matrix.
$group must be one of: system, members, important, commerce, other. */
public static string $group = 'other';
public static int $groupPriority = 5;
public static int $itemPriority = 3;
/* 534. Whether this admin may have this notification type AT ALL.
Controls whether the row appears on the settings matrix, and is the
default implementation of visibleTo(). Default TRUE. */
public static function permissionCheck( Member $member ) : bool;
/* 552. Per-notification (rather than per-type) visibility. Called once
per admin when the ID cache is built, and again for every candidate
email recipient. Defaults to permissionCheck(). */
public function visibleTo( Member $member ) : bool;
/* 564. Only consulted when severity() is SEVERITY_DYNAMIC. Default
FALSE, which means "never show". */
public function dynamicShow( Member $member ) : bool;
/* 575. Checked every time the notification is listed. Returning TRUE
DELETES the record, for every administrator. Default FALSE. */
public function selfDismiss() : bool;
/* 585. Whether the "view" toggle defaults to on for an admin who has
never saved the settings form. Default TRUE. */
public static function defaultValue() : bool;
/* 595. FALSE disables the "view" toggle and forces it on. */
public static function mayBeOptional() : bool;
/* 605. FALSE removes the "email me every time" option, leaving only
never/once. */
public static function mayRecur() : bool;
/* 617. Replace the whole email Select on the settings matrix with your
own form field. Default NULL. */
public static function customEmailConfigurationSetting( string $key, mixed $value ): ?FormAbstract;
/* 628. WHERE clause appended to the core_acp_notifications_preferences
query that picks email recipients. Default: on first send, admins set
to 'once' or 'always'; on a resend, only 'always'. */
public function emailWhereClause( mixed $extraForEmail ): array;
/* 652. Plain text, no HTML. Shown only in the bell dropdown, under the
title. Default NULL. */
public function subtitle() : ?string;
/* 683. One of the four STYLE_* constants; drives the CSS class.
Default derives from severity(): critical -> error, high -> warning,
everything else -> information. */
public function style() : string;
/* 701. Target of the bell dropdown row. Default links to the
notification centre with this notification highlighted. */
public function link() : Url|null;
}
The constants, from lines 50–110:
SEVERITY_DYNAMIC = 'dynamic',SEVERITY_OPTIONAL = 'optional',SEVERITY_NORMAL = 'normal',SEVERITY_HIGH = 'high',SEVERITY_CRITICAL = 'critical'.DISMISSIBLE_NO = 'no',DISMISSIBLE_TEMPORARY = 'temp',DISMISSIBLE_UNTIL_RECUR = 'recur',DISMISSIBLE_PERMANENT = 'perm'.STYLE_ERROR,STYLE_WARNING,STYLE_INFORMATION,STYLE_EXPIRE.
Only SEVERITY_HIGH and SEVERITY_CRITICAL produce a banner (applications/core/dev/html/admin/global/globalTemplate.phtml line 149). Only SEVERITY_CRITICAL reaches the front end (applications/core/dev/html/front/global/updateWarning.phtml line 3, included from applications/core/dev/html/front/global/globalTemplate.phtml line 129).
Raising and clearing a notification
Your class defines the type; it does not decide when a notification exists. That is done by calling three static methods on AdminNotification from anywhere — a task, a controller, a queue worker, an upgrade step.
/* Line 337 */ public static function send( string $app, string $extension, ?string $extra = NULL, ?bool $resend = TRUE, mixed $extraForEmail = NULL, bool|Member $bypassEmail = FALSE, array $additionalData = [] ) : void /* Line 303 */ public static function find( string $app, string $extension, ?string $extra = NULL, bool $forceRebuild=true ) : ?static /* Line 418 */ public static function remove( string $app, string $extension, ?string $extra = NULL, ?DateTime $newTime = NULL ) : void
A notification is identified by the triple app + ext + extra, compared with === at line 317. $extra is your discriminator: core's ConfigurationError uses one class for a dozen distinct problems ('siteOffline', 'tasksNotRunning', 'failedMail', "taskLock-{$id}"…) and branches on $this->extra inside title(), severity(), style() and visibleTo(). Pass $resend = FALSE to mean "raise it if it is not already there, otherwise leave it alone" (line 342). $additionalData is JSON-encoded into the additional_data column and read back through $this->additionalData (lines 488–501).
Clearing is your responsibility too. Core does it explicitly — applications/core/modules/admin/settings/licensekey.php line 160 calls AdminNotification::remove( 'core', 'License', 'missing' ) once a key is entered. The alternative is selfDismiss(), which lets the notification decide, at render time, that it is no longer relevant and delete itself.
Who calls it
applications/core/modules/admin/overview/notifications.phpline 57 —manage()callsAdminNotification::notifications()for the notification centre page and for the AJAX payload behind the bell dropdown.- The same file, lines 105–212 —
settings(). This is the only consumer that touches the static side of your class. It iteratesApplication::allExtensions( 'core', 'AdminNotifications', TRUE, NULL, NULL, FALSE )(line 144, note$construct = FALSE, so it receives class-name strings) and callspermissionCheck(),settingsTitle(),defaultValue(),mayBeOptional(),mayRecur(),customEmailConfigurationSetting()and reads$group,$groupPriority,$itemPriority. applications/core/dev/html/admin/global/globalTemplate.phtmlline 76 —notificationCount(), on every AdminCP page render. Line 149 renders the high/critical banners, callingstyle(),title(),body()anddismissible()on each. Skipped on thenotificationsandupgradecontrollers.applications/core/dev/html/front/global/updateWarning.phtml— critical notifications on the front end, for administrators only.applications/core/modules/front/system/ajax.phpline 687 —dismissAcpNotification(), the dismiss link on the front-end banner.system/Application/Application.phpline 5509 — on uninstall, iterates$this->extensions( 'core', 'AdminNotifications', FALSE )and deletes each type's rows fromcore_acp_notifications_preferences. Rows incore_acp_notificationsitself are deleted by app key a few lines earlier.applications/core/modules/admin/developer/details.phplines 567 and 760 — the Developer Center's missing-language-string scan, which knows to look foracp_notification_{key}(line 567) andmailsub__{app}_acp_notification_{key}(the loop at line 760, string built at line 762).AdminNotification::allNotifications()(line 266) andnotificationIdsForMember()(line 201) are the internal machinery all of the above funnel through.
Failure mode: extending the wrong base class
Because there is no AdminNotificationsAbstract, the mistake most people make first is inventing one, or writing a plain class. Nothing validates this at registration time. Application::getExtensionClass() (system/Application/Application.php line 548) only reads extensions.json and returns the string. The failure happens later. new $classname in send() (line 353) or constructFromData() (line 446) succeeds, and the property writes that follow (app, ext, extra, additionalData, lines 354–357) merely create dynamic properties — a PHP 8.2 deprecation, not an error. The first hard failure in send() is $notification->emailWhereClause( $extraForEmail ) at line 374, immediately followed by $notification->save() at line 377: Error: Call to undefined method. That is a fatal error on whichever request triggered the notification, which is often a background task rather than a page you are looking at. Check the system log, not the screen.
The same applies to constructors: core always calls new $classname with no arguments. A constructor with a required parameter is an ArgumentCountError at the same point.
Failure mode: severity() returns something that is not a SEVERITY_* constant
This is the quietest failure in the extension point, and core itself contains an instance of it.
notificationIdsForMember() buckets each notification by whatever severity() returned (line 251):
$data[ $member->member_id ][ $notification->severity() ][ $notification->id ] = $notification->id;
PHP happily creates a bucket named after your bogus string. notifications() then filters with in_array( $severity, $severities ) (line 179) against the five known names, and your notification is never in the list. notificationCount() only sums the five known buckets (line 156), so the bell count does not move either. The row is written to core_acp_notifications, no exception is thrown, nothing is logged, and the notification is invisible forever. The only way to notice is to query the table and find a row that never renders.
Core's applications/core/extensions/core/AdminNotifications/ConfigurationError.php line 579 has exactly this shape — for $extra === 'groupPromotionGroup' its severity() returns a translated language string rather than a constant. No code path in 5.0.19 sends that particular notification, so it is latent, but it is a good illustration of how easily the mistake survives review.
Failure mode: SEVERITY_DYNAMIC without dynamicShow()
SEVERITY_DYNAMIC exists for notifications that show to some administrators and not others for reasons that cannot be cached. It is the only severity whose display is decided at render time, by dynamicShow() (line 148 and line 179). The base implementation returns FALSE (line 564). So declaring SEVERITY_DYNAMIC and forgetting to override dynamicShow() produces a notification that is stored, counted as zero, and never displayed — with no error.
Worth knowing before you reach for it: no first-party extension in 5.0.19 uses SEVERITY_DYNAMIC. Grepping applications/ for SEVERITY_DYNAMIC and dynamicShow turns up only the base class and the Developer Center's severity dropdown in applications/core/Application.php (the {severity} Select, lines 892–899; the single SEVERITY_DYNAMIC reference is line 896). It is therefore an untested path in practice. The docblock at line 48 warns that it "uses more resources than the other types".
Failure mode: an exception in title(), body(), severity() or style()
None of the four call sites wrap your code in a try. It does not disappear, though: IPS::exceptionHandler() (init.php line 817, registered at line 633) catches everything uncaught, writes it to the system log under uncaught_exception, and serves an HTTP 500 error page. So the symptom is a hard error page plus a log entry, not silence. For a SEVERITY_NORMAL or SEVERITY_OPTIONAL notification the blast radius is the notification centre and the bell dropdown, which is bad but contained.
For SEVERITY_HIGH and SEVERITY_CRITICAL it is not contained. The banner block in globalTemplate.phtml runs title() and body() on every AdminCP page load. If yours throws, every AdminCP page 500s. The banner block is skipped on the notifications and upgrade controllers, but that does not save the notification centre: admin/notifications/index.phtml renders each notification through indexBlock.phtml, which calls style(), dismissible(), title() and body() itself (lines 3–14), so that page breaks too. The upgrader is the only page that escapes. For SEVERITY_CRITICAL the same methods also run inside updateWarning.phtml on every front-end page load for any logged-in administrator, so the site appears broken to staff while remaining fine for everyone else — a symptom that is very easy to misattribute.
Note also that severity() is reached from notificationCount()'s code path (line 251) whenever the acpNotificationIds datastore entry has to be rebuilt for that administrator — not on literally every ACP page, but on the first one after any notification is saved or deleted, which in practice is often. dynamicShow() is called on every notificationCount() call (line 148). Keep all four methods cheap and defensive. If body() needs to hit the database or a remote service, wrap it and return a plain string on failure. title() is declared : string, so returning NULL is a TypeError, not an empty heading.
Failure mode: the namespace is not exactly six segments
If you put the class somewhere else — a subdirectory, or an extensions folder nested differently — $exploded[5] is either the wrong word or undefined. The consequences are spread out and none of them is an obvious error:
- The settings matrix stores preferences under the wrong key, so the "view" and "email" choices an administrator makes are never read back by
notificationIdsForMember()(line 224). send()builds the email recipient query from the same key (line 372), so emails go to nobody.- The email template name is
'acp_notification_' . $exploded[5](line 399), so the wrong template is requested. - Uninstalling your app fails to clean up its preference rows (
Application.phpline 5511).
If $exploded[5] is undefined you will at least get an "Undefined array key 5" warning; if it merely holds the wrong word, you get nothing at all.
Failure mode: the extension key is not resolvable
Application::getExtensionClass() throws OutOfRangeException when the app is disabled, when recovery mode is on and the app is third-party, or when the key is absent from extensions.json (lines 550–571). Two callers handle that very differently.
allNotifications() catches it and deletes the row (lines 284–288):
catch( OutOfRangeException )
{
/* Remove orphan entry */
Db::i()->delete( 'core_acp_notifications', ['id=?', $notification['id']] );
}
That is intentional garbage collection for uninstalled apps, but it also means a typo in extensions.json, or a lower-case key that mb_ucfirst() cannot match, causes your notifications to be silently destroyed the first time any administrator loads an ACP page. They do not accumulate; they disappear.
The recovery-mode branch has the same effect. allNotifications() checks Application::appIsEnabled() first (line 276), which still passes for an enabled third-party app, and getExtensionClass() then throws because of RECOVERY_MODE (lines 561–564). So loading an ACP page in recovery mode deletes every stored notification belonging to a third-party app.
send() catches the same exception and then carries straight on to use the variable it failed to assign (lines 350–371). $notification is not undefined — it was assigned NULL by the if ( $notification = static::find( ... ) ) at line 340 — but it is still NULL when the catch block falls through. What happens next depends on $resend:
- With the default
$resend = TRUE: line 365 reads$notification->_newonNULL, which in PHP 8 is a warning evaluating toNULL, so the guard does not return; line 371 then passesNULLtoget_class(), which in PHP 8 is aTypeError. A bad key produces a fatal error at the point of sending rather than a clean exception you can catch. - With
$resend = FALSE:!$notification->_newisTRUEand!$resendisTRUE, so line 367 returns. The call does nothing at all, silently.
Both readings are from the 5.0.19 source; the exact messages emitted have not been verified on a running installation.
One further consequence of getExtensionClass() reading extensions.json from disk on every call: send(), find() and allNotifications() do not depend on the Store::i()->extensions datastore cache. The settings matrix does, because it goes through Application::allExtensions(). So it is possible for your notifications to send and display correctly while your row is missing from Notification Settings, if that cache is stale — including the recovery-mode case, where allExtensions() skips third-party apps (Application.php lines 393–397) and then writes the filtered result to the datastore. Clearing the system cache fixes it.
Failure mode: missing language strings
Three keys matter. The first two are fetched through Lang::addToStack(), which does not throw: when the word is missing, Lang::replaceWords() substitutes the key itself (system/Lang/Lang.php lines 2131–2135), so the symptom is raw acp_notification_MyThing text on screen. The third is different — see below.
acp_notification_{Key}— whateversettingsTitle()returns, which by convention is this. Shown as the row label on Notification Settings.acp_notification_group_{group}— the heading above your row. Only five exist, inapplications/core/dev/lang.phplines 7887–7891:important,system,members,commerce,other. A custom$groupvalue works functionally but renders a raw key as its heading, and you cannot add the string from your own app unless you define it yourself.mailsub__{app}_acp_notification_{Key}— the email subject. This one does throw.Email::compileSubject()calls$language->get( "mailsub__{$this->templateApp}_{$this->templateKey}" )(system/Email/Email.phpline 626), andLang::get()throwsUnderflowException( 'lang_not_exists__' . $key )when the key resolves to nothing (system/Lang/Lang.phplines 859–861). Nothing on that path catches it. Only reached if the email path is used.
In IN_DEV, the Developer Center's "missing strings" scan will list the first and third for you (details.php lines 567 and 762). On a built application there is no such check.
Failure mode: the email template does not exist
When an administrator opts in to emails, send() does this (lines 399–400):
$email = Email::buildFromTemplate( $exploded[1], 'acp_notification_' . $exploded[5], array( $notification, $extraForEmail ), Email::TYPE_TRANSACTIONAL ); $email->setUnsubscribe( 'core', 'unsubscribeAcpNotification', array( get_class( $notification ) ) );
So you need applications/{app}/dev/email/acp_notification_{Key}.phtml and .txt. Email::template() (system/Email/Email.php lines 1015–1039) does file_get_contents() in IN_DEV and a ->first() on core_email_templates otherwise; neither degrades gracefully when the template is absent.
This is easy to get away with for a long time, because the email path is opt-in. The email column of core_acp_notifications_preferences defaults to never, a row only exists at all once an administrator has saved the Notification Settings form, and the default emailWhereClause() selects only once/always rows. Core ships notification types with no email template for this reason — nexus/ConfigurationError and nexus/Maxmind both lack one. Your extension will work fine in testing and break for the first administrator who ticks the box.
Failure mode: cached visibility
notificationIdsForMember() caches, per administrator, the complete list of notification IDs they may see, in the acpNotificationIds datastore key (lines 203–258). The results of permissionCheck(), visibleTo(), defaultValue() and the dismissal checks are all baked into it.
That cache is invalidated when an AdminNotification record is saved or deleted ($caches at line 121, cleared by ActiveRecord::save() and ::delete() at system/Patterns/ActiveRecord.php lines 553 and 577), when the administrator saves Notification Settings, and when they dismiss something. It is not invalidated by anything else. If your visibleTo() depends on a setting, a group change or a moderator permission, the administrator's view will not update until some notification happens to be saved or deleted. Keep visibleTo() a function of the notification and the member's ACP restrictions, and nothing else.
Dismissal is not enforced server-side
dismissible() governs whether the templates draw a dismiss link (indexBlock.phtml, globalTemplate.phtml line 158, updateWarning.phtml). The dismiss routes themselves do not consult it: notifications::dismiss() (line 84) checks CSRF and calls AdminNotification::dismissNotification( Request::i()->id ), and the front-end route additionally requires Member::loggedIn()->isAdmin(). Neither asks whether the notification is dismissible. An administrator who constructs the URL by hand can therefore hide a DISMISSIBLE_NO notification. Do not rely on DISMISSIBLE_NO as a guarantee that a warning cannot be silenced.
The dismissal semantics themselves are worth reading once (lines 232–246): DISMISSIBLE_TEMPORARY expires after 86400 seconds, at which point the dismissal row is deleted and the notification returns. DISMISSIBLE_UNTIL_RECUR returns whenever the notification's sent timestamp is newer than the dismissal, which is what makes send() with $resend = TRUE meaningful. DISMISSIBLE_PERMANENT and DISMISSIBLE_NO both hide the notification permanently once a dismissal row exists.
Remember that selfDismiss() is different in kind: it calls delete() (line 184), removing the notification for every administrator at once, from inside a read path.
A minimal working example
This is applications/core/extensions/core/AdminNotifications/License.php reduced to its skeleton and renamed. It uses $extra to carry state, which is the pattern every non-trivial first-party implementation follows.
applications/myapp/extensions/core/AdminNotifications/SyncFailed.php:
<?php
namespace IPS\myapp\extensions\core\AdminNotifications;
use IPS\core\AdminNotification;
use IPS\Http\Url;
use IPS\Member;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
class SyncFailed extends AdminNotification
{
public static string $group = 'system';
public static int $groupPriority = 3;
public static int $itemPriority = 2;
public static function settingsTitle(): string
{
return 'acp_notification_SyncFailed';
}
public static function permissionCheck( Member $member ): bool
{
return $member->hasAcpRestriction( 'myapp', 'settings' );
}
public function title(): string
{
return Member::loggedIn()->language()->addToStack( 'myapp_sync_failed_title' );
}
public function subtitle(): ?string
{
return $this->sent->relative();
}
public function body(): ?string
{
/* $this->extra is whatever was passed to send(). Escape it. */
return Member::loggedIn()->language()->addToStack(
'myapp_sync_failed_body',
FALSE,
array( 'sprintf' => array( htmlspecialchars( $this->extra, ENT_QUOTES, 'UTF-8', FALSE ) ) )
);
}
public function severity(): string
{
return static::SEVERITY_HIGH;
}
public function dismissible(): string
{
return static::DISMISSIBLE_UNTIL_RECUR;
}
public function link(): Url
{
return Url::internal( 'app=myapp&module=settings&controller=sync' );
}
}
applications/myapp/data/extensions.json:
{
"core": {
"AdminNotifications": {
"SyncFailed": "IPS\\myapp\\extensions\\core\\AdminNotifications\\SyncFailed"
}
}
}
applications/myapp/dev/lang.php:
'acp_notification_SyncFailed' => "A scheduled sync failed", 'myapp_sync_failed_title' => "A scheduled sync failed", 'myapp_sync_failed_body' => "The last sync attempt failed with: %s",
Raising and clearing it, from wherever the sync runs:
use IPS\core\AdminNotification; /* Failed. Bump it if it is already there, so a dismissal expires. */ AdminNotification::send( 'myapp', 'SyncFailed', $errorCode ); /* Recovered. */ AdminNotification::remove( 'myapp', 'SyncFailed', $errorCode );
Add applications/myapp/dev/email/acp_notification_SyncFailed.phtml and .txt, plus the language key mailsub__myapp_acp_notification_SyncFailed, before shipping — otherwise the first administrator who opts in to emails will trigger an error rather than receive one.
A note on core's Custom notification
Core ships applications/core/extensions/core/AdminNotifications/Custom.php, which reads its title, subtitle, body, severity and link out of $additionalData so that code can raise an ad-hoc notification without defining a type. applications/core/setup/upg_500001/upgrade.php line 234 uses it this way.
Be careful with it, but be careful about the reason too. Custom::send() (line 159) overrides the parent to set the mail subject, and its call to parent::send() at line 164 omits the $additionalData argument entirely, so anything routed through Custom::send() loses the array. That override is not what core exercises: the upgrade step calls AdminNotification::send( 'core', 'Custom', '', FALSE, NULL, FALSE, [ ... ] ), a static call resolved on the base class, so Custom::send() is never entered and $additionalData is set normally at line 357. Nothing in 5.0.19 calls Custom::send() explicitly, so the dropped argument is latent — but it will bite you if you write Custom::send( ... ) yourself, which is the natural spelling. Core also ships no acp_notification_Custom email template. All of this is read from the 5.0.19 source; the runtime behaviour has not been verified on a live installation. Defining your own extension is more predictable than relying on Custom.
Verified against
Invision Community 5.0.19 (applications/core/data/versions.json, highest — that is, last — entry 5001908 => "5.0.19"). Files read in full: applications/core/sources/AdminNotification/AdminNotification.php; all eleven implementations under applications/core/extensions/core/AdminNotifications/; all five under applications/nexus/extensions/core/AdminNotifications/; applications/core/modules/admin/overview/notifications.php; applications/core/data/defaults/extensions/AdminNotifications.txt. Also inspected: Application::allExtensions(), Application::extensions(), Application::getExtensionClass() and the uninstall routine in system/Application/Application.php; system/Patterns/ActiveRecord.php; system/Email/Email.php; applications/core/data/schema.json for core_acp_notifications, core_acp_notifications_preferences and core_acp_notifcations_dismissals (the missing "i" in the dismissals table name is core's, not a typo here); the templates admin/global/globalTemplate.phtml, admin/notifications/index.phtml, indexBlock.phtml, popupList.phtml, front/global/updateWarning.phtml; applications/core/modules/front/system/ajax.php; and applications/core/modules/admin/developer/details.php. No implementations exist in forums, cms, downloads, gallery, blog, calendar or convert.
Recommended Comments