NotificationBell
extends BaseComponent
in package
Notification bell in the admin header: an icon button with a red dot and a Bootstrap dropdown with the last {@see PANEL_LIMIT} notifications.
Rendered in layouts/parts/shared/_header.latte, so on every admin page; the component needs no
registration, ComponentTrait resolves it from the name {control adminSystemNotificationBell}
through the DI factory.
The list is filled at page render; the user requests fresh data with the refresh button (signal
refresh!), which redraws the list and the badge.
IMPORTANT: the dropdown needs a single copy of Bootstrap JS. With two copies loaded, one click
called Dropdown::toggle() twice and the panel opened and closed at once. See the comment in
_head.latte.
Two snippets, not one:
notificationBadge: only the dot inside the button,notificationPanel: panel header and list.
IMPORTANT: never redraw the whole wrapper. Bootstrap keeps the dropdown instance on the toggle
element, so the panel would close under the user's hands on refresh. For the same reason the
toggle has data-bs-auto-close="outside": a click on refresh must not close it.
A notification has a type, not a text. The text column is practically empty; the type
(REGISTRATION_NEW, ORDER_NEW, ...) says what happened, and the id column carries the id of
the foreign record (not the own notification_id). The item label is therefore the type name and
the click-through leads straight to editing that record; the type-to-destination map lives in
NotificationTargetResolver, shared with the notification list. A notification without a
target (type without a page, or itemId NULL because the target record was deleted and the
notification detached from it) is plain text without a link; the cogwheel still offers the
notification detail.
Ownership: a row with receiverUserId = NULL is global (all admins see it and confirm it
together), a row with receiverUserId = <id> targets one user. The bell therefore shows and
marks only receiverUserId IS NULL OR receiverUserId = <logged-in user>
(buildScopeFilters()); without a logged-in identity the panel is empty. Another user's
targeted row is never shown or changed.
Table of Contents
Constants
- DETAIL_DESTINATION : mixed = ':Admin:System:Notification:edit'
- Notification detail: target of the cogwheel and fallback when the type has nowhere to link.
- FALLBACK_ICON : mixed = 'fa-solid fa-circle-info'
- Icon for a type without its own, so a row never has an empty icon.
- LIST_DESTINATION : mixed = ':Admin:System:Notification:show'
- Notification list: target of the "show all" button.
- PANEL_LIMIT : mixed = 10
- How many latest notifications the panel shows.
- TIME_FORMAT : mixed = 'j.n.Y H:i'
- Time format of a panel item (same as `DateTimeRenderer` in grids).
Properties
- $container : Container
- $routerDispatcher : RouterDispatcher
- $translator : ITranslator
- $appContext : AppContext
- $componentParameters : array<string, mixed>
- $currencyContext : CurrencyContext|null
- $deviceDetector : DeviceDetector|null
- $languageContext : LanguageContext|null
- $localizationContext : LocalizationContext|null
- $settingContext : SettingContext
- $themeContext : ThemeContext|null
- $translatorDomain : string|null
- $notificationManager : NotificationManager
- $notificationTargets : NotificationTargetResolver
Methods
- __call() : mixed
- Dispatches `renderXyz()` to `render('Xyz', ...$args)`.
- __construct() : mixed
- createComponent() : IComponent|null
- getAppContext() : AppContext
- getComponentParameter() : mixed
- Returns a component parameter, or null when it is not set.
- getContainer() : Container
- getCurrencyContext() : CurrencyContext|null
- getLanguageContext() : LanguageContext|null
- getLocalizationContext() : LocalizationContext|null
- getRouterDispatcher() : RouterDispatcher
- getSettingContext() : SettingContext
- getSettings() : array<int, array<string, mixed>>
- getThemeContext() : ThemeContext|null
- handleMarkAllRead() : void
- Marks all unread notifications as read (the dot goes out).
- handleRefresh() : void
- Manual list refresh (button in the panel header). The list is otherwise filled only at page load, so it gets stale in a long-open admin.
- hasServiceByType() : bool
- Returns whether the container has a service of the given type.
- initializeAllContexts() : void
- injectDeviceDetector() : void
- onCreate() : void
- render() : void
- setAppContext() : $this
- setComponentParameter() : void
- Sets a component parameter.
- setCurrencyContext() : $this
- setLanguageContext() : $this
- setLocalizationContext() : $this
- setSettingContext() : $this
- setThemeContext() : $this
- setup() : void
- setupAppContext() : void
- typedComponent() : T
- Returns a subcomponent with a verified type.
- frontPresenter() : BasePresenter|null
- Returns the front presenter the component is attached to (null outside the front end or when detached).
- getTranslator() : ITranslator
- setTemplateFile() : void
- setTranslator() : self
- buildScopeFilters() : array<int, Filter>
- Bell scope: global rows plus rows targeted at the given user. The OR is one group filter, so Doctrine parenthesizes it and `read = 0` applies to both branches.
- createMultiplierComponent() : Multiplier<string|int, IComponent>
- Creates a multiplier for the component.
- createNewComponentInstance() : IComponent|null
- Creates a new component instance without adding it to the presenter; used by the multiplier.
- createStandardComponent() : IComponent|null
- Creates a standard component (not a multiplier).
- findUnread() : array<int, Notification>
- hasUnread() : bool
- Tells whether the dot is lit. The dot means "an unread notification exists", any one, not just among the displayed ones. When it is visible in the loaded ten, the answer is ready and no extra query runs; the query is paid only when the last ten are all read.
- prepareItems() : array<int, array{id: int, title: string, text: string, time: string, read: bool, failed: bool, icon: string, href: string|null, detailHref: string}>
- Returns the panel items: the last {@see PANEL_LIMIT} notifications, newest first.
- redrawPanel() : void
- Redraws the list and the badge. Without JS (naja did not catch the link) it ends with a redirect to the same page: the panel closes, but the data is fresh after the reload, so the action is not lost.
- resolveHref() : string|null
- Returns the click-through of an item: editing of the record the notification was raised for (`itemId`, not `notification_id`). `null` makes the item plain text: a type without a target in the map, a target the admin has no privilege for, or a notification without `itemId`, which stays detached after the target record is deleted and must not lead to a 404.
- resolveUserId() : int|null
- Returns the logged-in admin id, or null without an identity (then nothing is shown or marked).
Constants
DETAIL_DESTINATION
Notification detail: target of the cogwheel and fallback when the type has nowhere to link.
private
mixed
DETAIL_DESTINATION
= ':Admin:System:Notification:edit'
FALLBACK_ICON
Icon for a type without its own, so a row never has an empty icon.
private
mixed
FALLBACK_ICON
= 'fa-solid fa-circle-info'
Font Awesome comes from a CDN in the admin (_head.latte).
LIST_DESTINATION
Notification list: target of the "show all" button.
private
mixed
LIST_DESTINATION
= ':Admin:System:Notification:show'
PANEL_LIMIT
How many latest notifications the panel shows.
private
mixed
PANEL_LIMIT
= 10
TIME_FORMAT
Time format of a panel item (same as `DateTimeRenderer` in grids).
private
mixed
TIME_FORMAT
= 'j.n.Y H:i'
Properties
$container
public
Container
$container
Attributes
- #[Inject]
$routerDispatcher
public
RouterDispatcher
$routerDispatcher
$translator
public
ITranslator
$translator
Tags
$appContext
protected
AppContext
$appContext
$componentParameters
protected
array<string, mixed>
$componentParameters
= []
component parameters set by setComponentParameter()
$currencyContext
protected
CurrencyContext|null
$currencyContext
= NULL
$deviceDetector
protected
DeviceDetector|null
$deviceDetector
= NULL
$languageContext
protected
LanguageContext|null
$languageContext
= NULL
$localizationContext
protected
LocalizationContext|null
$localizationContext
= NULL
$settingContext
protected
SettingContext
$settingContext
$themeContext
protected
ThemeContext|null
$themeContext
= NULL
$translatorDomain
protected
string|null
$translatorDomain
= 'admin.system.components.notificationBell'
$notificationManager read-only
private
NotificationManager
$notificationManager
$notificationTargets read-only
private
NotificationTargetResolver
$notificationTargets
Methods
__call()
Dispatches `renderXyz()` to `render('Xyz', ...$args)`.
public
__call(string $name, array<string|int, mixed> $args) : mixed
Called only when the component has no renderXyz() method of its own.
Parameters
- $name : string
- $args : array<string|int, mixed>
__construct()
public
__construct(NotificationManager $notificationManager, NotificationTargetResolver $notificationTargets) : mixed
Parameters
- $notificationManager : NotificationManager
- $notificationTargets : NotificationTargetResolver
createComponent()
public
createComponent(string $name) : IComponent|null
Parameters
- $name : string
Return values
IComponent|nullgetAppContext()
public
getAppContext() : AppContext
Return values
AppContextgetComponentParameter()
Returns a component parameter, or null when it is not set.
public
getComponentParameter(string $name) : mixed
Parameters
- $name : string
getContainer()
public
getContainer() : Container
Return values
ContainergetCurrencyContext()
public
getCurrencyContext() : CurrencyContext|null
Return values
CurrencyContext|nullgetLanguageContext()
public
getLanguageContext() : LanguageContext|null
Return values
LanguageContext|nullgetLocalizationContext()
public
getLocalizationContext() : LocalizationContext|null
Return values
LocalizationContext|nullgetRouterDispatcher()
public
getRouterDispatcher() : RouterDispatcher
Return values
RouterDispatchergetSettingContext()
public
getSettingContext() : SettingContext
Return values
SettingContextgetSettings()
public
getSettings() : array<int, array<string, mixed>>
Return values
array<int, array<string, mixed>>getThemeContext()
public
getThemeContext() : ThemeContext|null
Return values
ThemeContext|nullhandleMarkAllRead()
Marks all unread notifications as read (the dot goes out).
public
handleMarkAllRead() : void
Without it 100+ old unread records could be handled only one by one and the dot would stay lit forever.
Attributes
- #[Requires]
- $methods: 'POST'
handleRefresh()
Manual list refresh (button in the panel header). The list is otherwise filled only at page load, so it gets stale in a long-open admin.
public
handleRefresh() : void
hasServiceByType()
Returns whether the container has a service of the given type.
public
hasServiceByType(string $className) : bool
The name is composed dynamically (module plus component), so class_exists() runs first, and
a true result guarantees a valid type.
Parameters
- $className : string
Tags
Return values
boolinitializeAllContexts()
public
initializeAllContexts() : void
injectDeviceDetector()
public
injectDeviceDetector(DeviceDetector $detector) : void
Parameters
- $detector : DeviceDetector
onCreate()
public
onCreate() : void
render()
public
render() : void
setAppContext()
public
setAppContext(AppContext $appContext) : $this
Parameters
- $appContext : AppContext
Return values
$thissetComponentParameter()
Sets a component parameter.
public
setComponentParameter(string $name, mixed $value) : void
Parameters
- $name : string
- $value : mixed
setCurrencyContext()
public
setCurrencyContext(CurrencyContext|null $currencyContext) : $this
Parameters
- $currencyContext : CurrencyContext|null
Return values
$thissetLanguageContext()
public
setLanguageContext(LanguageContext|null $languageContext) : $this
Parameters
- $languageContext : LanguageContext|null
Return values
$thissetLocalizationContext()
public
setLocalizationContext(LocalizationContext|null $localizationContext) : $this
Parameters
- $localizationContext : LocalizationContext|null
Return values
$thissetSettingContext()
public
setSettingContext(SettingContext $settingContext) : $this
Parameters
- $settingContext : SettingContext
Return values
$thissetThemeContext()
public
setThemeContext(ThemeContext|null $themeContext) : $this
Parameters
- $themeContext : ThemeContext|null
Return values
$thissetup()
public
setup(BasePresenter|BasePresenter $presenter) : void
Parameters
- $presenter : BasePresenter|BasePresenter
setupAppContext()
public
setupAppContext(AppContext $appContext) : void
Parameters
- $appContext : AppContext
typedComponent()
Returns a subcomponent with a verified type.
public
typedComponent(string $name, T> $type) : T
Use it instead of an inline @var above $this['x'] (which does not narrow the type) and
instead of the generic IComponent that $this['x'] returns. See
Form::typedComponent().
Parameters
- $name : string
- $type : T>
Tags
Return values
TfrontPresenter()
Returns the front presenter the component is attached to (null outside the front end or when detached).
protected
frontPresenter() : BasePresenter|null
Return values
BasePresenter|nullgetTranslator()
protected
getTranslator() : ITranslator
Return values
ITranslatorsetTemplateFile()
protected
setTemplateFile(string $dir, string $filename) : void
Parameters
- $dir : string
- $filename : string
setTranslator()
protected
setTranslator(ITranslator $translator) : self
Parameters
- $translator : ITranslator
Return values
selfbuildScopeFilters()
Bell scope: global rows plus rows targeted at the given user. The OR is one group filter, so Doctrine parenthesizes it and `read = 0` applies to both branches.
private
buildScopeFilters(int $userId[, bool $onlyUnread = false ]) : array<int, Filter>
Parameters
- $userId : int
- $onlyUnread : bool = false
Return values
array<int, Filter>createMultiplierComponent()
Creates a multiplier for the component.
private
createMultiplierComponent(string $name) : Multiplier<string|int, IComponent>
Parameters
- $name : string
Return values
Multiplier<string|int, IComponent>createNewComponentInstance()
Creates a new component instance without adding it to the presenter; used by the multiplier.
private
createNewComponentInstance(string $baseName[, int|string|null $uniqueKey = null ]) : IComponent|null
Parameters
- $baseName : string
- $uniqueKey : int|string|null = null
Return values
IComponent|nullcreateStandardComponent()
Creates a standard component (not a multiplier).
private
createStandardComponent(string $name) : IComponent|null
Parameters
- $name : string
Return values
IComponent|nullfindUnread()
private
findUnread(int $userId) : array<int, Notification>
Parameters
- $userId : int
Return values
array<int, Notification>hasUnread()
Tells whether the dot is lit. The dot means "an unread notification exists", any one, not just among the displayed ones. When it is visible in the loaded ten, the answer is ready and no extra query runs; the query is paid only when the last ten are all read.
private
hasUnread(array<int, array{read: bool}> $items, int $userId) : bool
Parameters
- $items : array<int, array{read: bool}>
-
items of the panel loaded just now
- $userId : int
Return values
boolprepareItems()
Returns the panel items: the last {@see PANEL_LIMIT} notifications, newest first.
private
prepareItems(int $userId) : array<int, array{id: int, title: string, text: string, time: string, read: bool, failed: bool, icon: string, href: string|null, detailHref: string}>
Each item has two targets: href is the record the notification was raised for (label on the left
plus the type icon on the right; null means no target, plain text), detailHref is editing of
the notification itself (cogwheel). icon is the Font Awesome class of the type, with the
placeholder FALLBACK_ICON.
Parameters
- $userId : int
Return values
array<int, array{id: int, title: string, text: string, time: string, read: bool, failed: bool, icon: string, href: string|null, detailHref: string}>redrawPanel()
Redraws the list and the badge. Without JS (naja did not catch the link) it ends with a redirect to the same page: the panel closes, but the data is fresh after the reload, so the action is not lost.
private
redrawPanel() : void
resolveHref()
Returns the click-through of an item: editing of the record the notification was raised for (`itemId`, not `notification_id`). `null` makes the item plain text: a type without a target in the map, a target the admin has no privilege for, or a notification without `itemId`, which stays detached after the target record is deleted and must not lead to a 404.
private
resolveHref(Notification $notification) : string|null
Parameters
- $notification : Notification
Return values
string|nullresolveUserId()
Returns the logged-in admin id, or null without an identity (then nothing is shown or marked).
private
resolveUserId() : int|null