CoolCMS API

SchedulePresenter extends BasePresenter
in package

FinalYes

Schedule calendar administration (`/admin/company/schedule`): year, month, week and day views over the availability of a company calendar.

The month overview (renderShow() + decorateOverview()) reads the Api narrow action availability with a calendarId (bridge, TimeSlotManager::availabilityForCalendar()), not raw entities. Days without an own row (source = 'default', the implicit grid from working hours) have nothing for a raw grid over TimeSlot to list; only the Api knows the authoritative resolution (closed_day, then override slots, then the default grid). Do not duplicate that logic in Admin. The payload carries state (closed|free|partial|full) and freeMinutes: contiguous free minutes of the day after subtracting occupied intervals (reservations and blocks), not grid slots. windowCount/freeCount count contiguous windows and free intervals.

Table management of overrides and blocks (DataGrid + add/edit form) lives in TimeSlotPresenter, not here.

Table of Contents

Constants

LOGIN_BACKLINK  : string = 'backlink'
Login page parameter with the key of the stored request to return to ({@see loginReturnArgs()}).
DEFAULT_ITEMS_PER_PAGE  : mixed = 20
Items per page of admin lists when the DB setting is missing.
FORM_BACK_SUBSECTION  : mixed = 'formBack'
Session subsection mapping a key to the previous page URL of edit forms.
SESSION_NAMESPACE  : mixed = 'admin'
BLOCKED_EXTENSIONS  : mixed = ['php', 'php3', 'php4', 'php5', 'php7', 'php8',...
Extensions that must not be uploaded into the web-served folder; otherwise an editor upload would mean running foreign code on the server. It is no content whitelist (the editor uploads what is needed), only a safeguard against executable files.
DAY_HOUR_HEIGHT_PX  : int = 72
Pixels per hour in the day timeline. IMPORTANT: must match `--cal-hour-height` (`abstracts/_vars.scss`); `day.latte` receives precomputed `axisHeightPx`/`topPx`/`heightPx`, so changing one side without the other misaligns the timeline.
EDITOR_PAGE_LIMIT  : mixed = 500
Listing cap; the dialog is a suggester, not a paged list.
EDITOR_PAGE_TARGETS  : mixed = ['front:system:page:show']
Routes the editor may link to: text pages only.
EDITOR_UPLOAD_FOLDER  : mixed = 'editorUploads'
Editor root (relative to the `uploads/` root). Changing it to `''` would open the whole `uploads/` tree to the manager; the editor user could then reach (and delete) files of other modules such as products and avatars.
IMAGE_EXTENSIONS  : mixed = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', '...
Extensions the file manager shows as a preview instead of a document icon.
LOCALE_MAP  : mixed = ['cs' => 'cs_CZ', 'en' => 'en_US', 'sk' => 'sk_...
Maps the short language code to the full locale of the registered NEON sources.
MINUTES_PER_DAY  : int = 1440
Minutes per day, used for the closed bookend bars ({@see makeBookendBlock()}).
MONTH_KEYS  : array<string|int, mixed> = ['01' => 'jan', '02' => 'feb', '03' => 'mar', '...
ISO month (`m`, `01`..`12`) to key `list.overview.months.*`, see {@see formatMonthLabel()}; same mapping as {@see \App\UI\Front\Company\Components\BookingCalendarComponent\Traits\InitTrait::MONTH_KEYS}.
WEEK_HOUR_WIDTH_PX  : int = 108
Pixels per hour in the week grid. IMPORTANT: must match `--cal-week-hour-width`.
WEEKDAY_KEYS  : array<string|int, mixed> = [1 => 'mon', 2 => 'tue', 3 => 'wed', 4 => 'thu'...
ISO weekday (`N`, 1..7) to key `list.overview.weekdays.*`, analog of {@see MONTH_KEYS} (the int-key trap does not apply, `N` has no leading zero).

Properties

$accessTokenValidator  : AccessTokenValidator
Signature and expiry alone are not enough: a revoked token (`jti` on the deny-list) that has not expired yet would keep the session logged in although the Api guard rejects the same token. Same contract as {@see \App\Core\Traits\Api\Presenters\JwtAuthTrait::checkJwtAuth()}, one implementation.
$adminPageManager  : AdminPageManager
$authManager  : AuthManager
$authStorageManager  : AuthStorageManager
$authUserManager  : UserManager
Admin `UserManager` for {@see UserManager::login()}. A module must not read through another module, so admin has its own `login()` that calls the same Api endpoint (`api/system/auth/login`).
$columnSchema  : ColumnSchema
Limits of the real database columns for {@see guardColumns()}.
$container  : Container
$deviceDetector  : DeviceDetector
$editorAutosaveScope  : EditorAutosaveScope
Scope of the editor autosave drafts, see {@see \App\Core\Security\EditorAutosaveScope}.
$editorLinkPageManager  : PageManager
NOTE: own property name. `$pageManager` already exists on presenters (`PagePickerTrait`), and two `#[Inject]` properties of the same name would collide in the trait composition.
$editorUploadManager  : FileSystemManager
NOTE: own property name, not `$fileSystemManager`.
$eventDispatcher  : EventDispatcher
$formSubmissionGuard  : FormSubmissionGuard
One-time submission tokens of "add" forms, see {@see guardFormSubmitOnce()}.
$jwtManager  : JwtManager
$languageContext  : LanguageContext
$languageManager  : LanguageManager
$localizationContext  : LocalizationContext
$localizationManager  : LocalizationManager
$loginDeviceCookie  : LoginDeviceCookie
Cookie of a known device for the password login throttle ({@see self::rememberLoginDevice()}).
$modalWindowFactory  : ModalWindowFactory
Injected through Nette presenter inject (after construction), not the constructor.
$picker  : string
Picker mode of a list; when not empty, the presenter renders its `show` grid as a selection modal (no add/edit/delete/checkboxes, each row has a select button) in the minimal `@picker.latte` layout.
$pickerExclude  : int
Id of the record that cannot be selected in picker mode (its select button is hidden).
$pickerExcludeSubtreeLft  : int
`lft`/`rgt` bounds of a whole subtree whose nodes cannot be selected in picker mode.
$pickerExcludeSubtreeRgt  : int
$presenterContext  : PresenterContext
$privilegeManager  : PrivilegeManager
$queryCacheContext  : QueryCacheContext
Query cache policy for the whole admin request; `startup()` disables it.
$routerDispatcher  : RouterDispatcher
$settingContext  : SettingContext
$settingManager  : SettingManager
$themeContext  : ThemeContext
$themeManager  : ThemeManager
$themeResolver  : DefaultThemeResolver
$translator  : ITranslator
$translatorSessionResolver  : Session
$userManager  : UserManager
$adminPageEntity  : AdminPage|null
$debugMode  : bool
$productionMode  : bool
$savedEntityId  : int|null
Id of the record just saved; set by the `$processData` callback in `createForm()` after a successful save.
$translatorDomain  : string
$calendarManager  : CalendarManager
$companyManager  : CompanyManager
$isLoggingIn  : bool
$occupiedSlotIds  : array<int, true>|null
Memoized set `timeSlotId => true` of slots with an active reservation (`pending`/`confirmed`), see {@see loadOccupiedSlotIds()}.
$reservationManager  : ReservationManager
$slotModalFactory  : SlotModalFactory
$theme  : Theme|null
$timeSlotManager  : TimeSlotManager
$userPrivileges  : array<int, string>

Methods

__construct()  : mixed
actionDay()  : void
actionWeek()  : void
actionYear()  : void
beforeRender()  : void
createComponent()  : IComponent|null
flashFormOwnErrors()  : void
Flashes form-level errors (`$form->getOwnErrors()`) as `danger` messages.
flashRejectedUploads()  : void
Flashes one `warning` listing the files the Api rejected (type, dimensions, unreadable) after a successful save of a form with a dropzone ({@see UploadRejection}).
formatLayoutTemplateFiles()  : array<string|int, mixed>
Admin version of {@see \App\Core\Traits\Front\Presenters\ThemeInitTrait::formatLayoutTemplateFiles()}.
getAdminPageEntity()  : AdminPage|null
getContainer()  : Container
getFormBackUrl()  : string
Returns the stored previous page URL for the key, used by the `cancel` link in the template.
getModalWindow()  : ModalWindow
getRouterDispatcher()  : RouterDispatcher
getTranslator()  : ITranslator
guardFormSubmitOnce()  : void
Protects a form against a double submit: one rendered form saves at most once.
handleChangeLocale()  : void
handleEditorCreateFolder()  : void
Creates a subfolder in `folder` (path relative to the editor root).
handleEditorDelete()  : void
Deletes a file or (recursively) a folder. An empty path would point to the editor root, which cannot be deleted.
handleEditorFiles()  : void
Sends the folder listing for the editor file manager. The `folder` parameter is a path relative to the editor root (empty means the root).
handleEditorPages()  : void
Sends the page list for the link dialog as JSON `{"pages": [{id, label, url}, ...]}` sorted by label.
handleEditorRename()  : void
Renames a file or folder (`path` relative to the editor root).
handleEditorUpload()  : void
Accepts a file from the editor (field `file`, multipart POST; optional `folder` = subfolder of the editor root) and sends JSON `{"location": "/www/uploads/editorUploads/..."}`, the format TinyMCE expects from `images_upload_handler`.
handleLogout()  : void
handleMergeSlots()  : void
Merges adjacent time slots (button "Merge" above the day timeline).
handleSignOut()  : void
hasServiceByType()  : bool
Returns whether the container has a service of the given type.
initializeTranslator()  : void
isAddAction()  : bool
Tells whether the current action creates a record: `add` and the `add<Something>` actions (`addMenu`, `addKey`). Derived from the server context only, like {@see formSubmitPrivilege()}.
isAllowed()  : bool
Returns whether the logged-in user has the given privilege.
isSubmitOnceForm()  : bool
Tells whether the form was protected by {@see guardFormSubmitOnce()}.
loginWithCredentials()  : void
Logs in with username and password; called only by the login form (after validation and CSRF).
publishFieldErrors()  : void
Publishes element errors of a failed submit to the client.
redirectToFormBack()  : void
Redirects to the stored previous page and discards the key.
renderDay()  : void
Renders the day timeline of one calendar. Consumes the admin-only Api {@see \App\UI\Api\Company\Models\Managers\TimeSlotManager::dayDetailForCalendar()} (bridge {@see TimeSlotManager::dayDetail()}); gap filling into free/closed between windows is done by {@see buildDayTimeline()}. Without a calendar (the company has no active one) `dayDetail` stays `null` and the template shows a message instead of an empty card.
renderShow()  : void
`show` is the default action of admin presenters (fallback route).
renderWeek()  : void
Renders the week grid in two modes: one calendar (default) or all calendars of the company merged (`calendarId=0` in the URL; the switcher inside `week.latte` is a `<select>` with an extra "all calendars" option).
renderYear()  : void
Year overview: previous/next year buttons, the year, and a table of months (January to December).
resolveFormBackKey()  : string
Returns the previous-page key: from the URL if present (survives the update redirect), otherwise on a plain GET render of the edit page stores `Referer` in the session under a new random key.
runSubmitOnce()  : bool|null
Runs a saving step of a form under its one-time token (a form without the token just runs it).
setConfigParameters()  : void
setTranslator()  : $this
startup()  : void
typedComponent()  : T
Returns a subcomponent with a verified type.
authorize()  : void
Authorization hook; by default requires the `:Admin` privilege.
authorizeFormSubmit()  : void
Gate for writing presenter forms built by `createForm()`.
authorizeListing()  : void
Gate for reading lists.
checkAdminAccess()  : void
Entry gate to the admin: a guest is sent to login; a logged-in user without `:Admin` is sent to login with an error.
checkAuthentication()  : void
checkCurrentPrivilege()  : void
Builds the key from the current request (`:Admin:<Module>:<Presenter>:<action>`) and checks it. `getName()` already contains the `Admin:...` prefix, so only one leading colon is added.
checkPrivilege()  : void
Checks a specific privilege; throws 403 when denied.
createComponentModalWindow()  : ModalWindow
createComponentSlotModal()  : SlotModal
Slot edit modal. Templates do not mount it via `{control slotModal}`, they only generate `{link slotModal:open…!}` links; the content renders into the shared {@see \App\UI\Admin\System\Components\ModalWindowComponent\ModalWindow}.
createForm()  : Form
Creates an edit form with the standard actions and a return to the previous page.
domainTranslator()  : PrefixedTranslator|null
Returns a translator prefixed with the presenter domain, or `null` (empty domain or a translator other than Contributte).
formSubmitPrivilege()  : string
Returns the privilege key required to save a form built by `createForm()`.
getAdminItemsPerPage()  : int
Returns the default items per page of admin lists from the DB setting `admin.itemsPerPage` (module System = 1), falling back to {@see DEFAULT_ITEMS_PER_PAGE}.
getAdminLayout()  : string
Returns the layout name; in picker mode ({@see $picker}) the `show` grid uses the minimal `@picker.latte` (no sidebar/header) so the opener fetch returns only the table.
getFormBackFallback()  : string
Returns the return URL when the previous page is unknown; override in a presenter without a `show` action.
getSessionKey()  : string
Returns a session key string, e.g. 'front.auth.step1'.
getSessionSection()  : SessionSection
Returns a Nette\SessionSection instance using a dynamically built session key.
guardColumns()  : void
Gates every text and number field of the form by the real column it writes to ({@see \App\Core\Forms\ColumnGate::guard()}): an oversized value is an error at the field, never a database error that closes the entity manager (admin walkthrough A1, A14).
initializePresenterContext()  : void
isSameSiteRequest()  : bool
Tells whether the request comes from the own site (Nette SameSite cookie); protects GET actions with a side effect.
loadUserPrivileges()  : void
Loads all privileges of the logged-in user with one database query. Call it right after `checkAdminAccess()` in `startup()` (the user is guaranteed to be logged in by then).
logout()  : void
setTranslatorDomain()  : void
setupTemplateTranslator()  : void
Sets a translator prefixed with the presenter domain into the template (as Front components do, see {@see \App\Core\Base\BaseComponent::setup()}).
buildDayTimeline()  : array{axisStartHour: int, axisEndHour: int, labelStartHour: int, labelEndHour: int, axisHeightPx: int, hourHeightPx: int, blocks: list>}
Builds the day timeline blocks from the Api payload (`windows` + `segments`, both HH:MM): generic interval arithmetic, not the day resolution logic (that stays in the Api `dayDetailForCalendar()`).
buildMetaLabel()  : string
Builds the metadata row of a day in the grid: "free · H:MM" (free), "occupied · H:MM" (partial/full, summed from the payload `busy` intervals, see {@see sumBusyMinutes()}) or an empty string (closed). Requires `freeMinutesLabel` already computed (see {@see decorateOverview()}).
buildWeekAllData()  : array{axisStartHour: int, axisEndHour: int, axisWidthPx: int, hourWidthPx: int, rows: list>}
Builds week data of all company calendars merged: `numCalendars Ă— 7` bridge calls (small volumes, a company typically has a few calendars). A row per day holds `lanes`, one entry per calendar (`calendar` entity + its bubbles); the template builds nested `.cal-week__lane` rows.
buildWeekBubbles()  : array<int, array<string, mixed>>
Converts day segments into week grid bubbles (`leftPx`/`widthPx` on the common week axis, {@see WEEK_HOUR_WIDTH_PX} instead of {@see DAY_HOUR_HEIGHT_PX}, because the week is horizontal).
buildWeekDates()  : array<int, string>
Builds the 7 dates Monday to Sunday containing `$anyDateInWeek` (ISO `N` arithmetic, Monday = 1, same convention as {@see withPaddingDays()}). An invalid date falls back to today so the week is always built.
buildWeekSingleData()  : array{axisStartHour: int, axisEndHour: int, axisWidthPx: int, hourWidthPx: int, rows: list>}
Builds week data of one calendar: 7 bridge calls {@see TimeSlotManager::dayDetail()} (small volumes, same reasoning as {@see loadOccupiedSlotIds()}). Returns one row per day with bubbles on the common axis ({@see resolveWeekAxis()}).
buildYearMonths()  : array<int, array{month: string, label: string, isCurrent: bool, reservations: int, blocked: int, closedDays: int, hasAny: bool}>
Builds the twelve months of the year with a summary. Two queries over the whole year (calendar rows plus active reservations) and aggregation in PHP, with no Api call per month.
createIdentity()  : SimpleIdentity|null
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).
daySegmentKey()  : string
decorateOverview()  : array<int|string, mixed>
Adds to every day of the Api payload {@see \App\UI\Api\Company\Models\Managers\TimeSlotManager::availabilityForCalendar()} a readable `freeMinutesLabel` (`H:MM`) and a uniform `metaLabel` ("free · H:MM" / "occupied · H:MM", empty for `closed`), so the template only prints. Also adds padding days of the neighboring months ({@see withPaddingDays()}) so the grid always starts on the `$firstDow`-th weekday and ends on the day before it.
editorBreadcrumb()  : array<int, array{label: string, path: string}>
Builds the breadcrumb navigation: cumulative segment paths.
editorLinkLangId()  : int
Returns the language used to pick labels and URLs (default 1, Czech).
editorPageLabel()  : string
Returns the page label: title, otherwise name, otherwise `#id`.
editorPageRoute()  : Route|null
Returns the first page route the editor may link to. A page may have several (many-to-many); the whitelisted one is taken, not the first in order, so a page with both `Page:show` and `Page:form` ends up on the right one.
editorPageUrl()  : string|null
Returns the front end address of a page. The router builds it from the page route, so `/o-nas` and any prefix come out on their own.
editorParentFolder()  : string|null
Returns the parent folder (null in the editor root).
editorPath()  : string|null
Converts a client path (relative to the editor root) to an Api path (relative to the `uploads/` root).
editorUploadName()  : string|null
Builds a safe and unique file name. Sanitization copies the Api layer rules (webalize of the base and of the extension), so the name returned in the URL is exactly the name the file is stored under. The random suffix prevents collisions: `writeFile()` overwrites the same name without asking, so two different `obrazek.jpg` files would overwrite each other.
editorUploadUrl()  : string
Returns the public URL of a file. `$path` is relative to the `uploads/` root (so it includes `editorUploads/`), the same shape as other uploads in the system.
ensureEditorRoot()  : void
Creates the editor root folder; when it already exists the Api returns false and nothing happens.
expireIdleLogin()  : bool
Returns whether the login expired from inactivity. If so, revokes the tokens (like a logout), clears the `auth` section, logs out the Nette identity and returns true; otherwise moves the activity stamp forward.
findOutsideSegments()  : array<int, array<string, mixed>>
Returns the segments that did not land in any block of {@see buildDayTimeline()}: outside all offered windows (a reservation from before the working hours were narrowed or before a day override, or a block on a `closed_day`). Without this the admin would not know about such a reservation.
formatDayLabel()  : string
Builds a readable day label for the day timeline header ("Thursday 12. March 2026"). The weekday name uses its own key `list.overview.weekdays.*` in this presenter domain, see {@see WEEKDAY_KEYS}; not a cross-domain `//` to `calendar.form.openingHours.days.*`, because those are abbreviations for working hours and this is a full name for a header date.
formatFreeMinutesLabel()  : string
Converts minutes to `H:MM` (like Api `TimeSlotManager::formatMinutes()`, without a cross-module import).
formatIntervals()  : string
Formats Api payload intervals (`[{from, to}, …]`) as `09:00–10:00, 12:00–13:00`; empty input gives an empty string and the template then renders no badge. The payload is `mixed` (it came over the bridge), so every item is verified, with no bare casts.
formatMonthLabel()  : string
Builds a readable month label ("March 2026"). The project has no general Latte filter for a localized month name (PHP `date('F')` is English), so this follows the Front {@see \App\UI\Front\Company\Components\BookingCalendarComponent\Traits\InitTrait} (ISO month to `months.*` key, translated via the translator) within this admin presenter, with no cross-module import of a Front component.
formatWeekRangeLabel()  : string
Builds the week range label ("10.–16. March 2026" within one month; otherwise "24. August – 30. August 2026", with the year when that differs too). Readability, not canvas-exact typography.
getClientIp()  : string
initializeMetaTags()  : void
Fills the template with the title, meta tags and HTML text of the page from the translation (`PageText`) of the current language, for the layout (like the Front {@see \App\Core\Traits\Front\Presenters\PageInitTrait}).
initializePage()  : void
isDevelopment()  : bool
loadLocalization()  : Localization|null
loadOccupiedSlotIds()  : array<int, true>
Returns the set of slots with an active reservation (`pending`/`confirmed`). Loads all active reservations of the Admin {@see ReservationManager} in one query (small volumes, no query per day or month). Memoized in a property; only {@see buildYearMonths()} calls it.
localizationCurrencyKey()  : string|null
Admin ignores the localization currency (it takes the language only).
loginReturnArgs()  : array<string, string>
Remembers the request of a logged-out user so the login can return to it ({@see storeRequest()}, `backlink` of the login page, restored by `AuthStorageTrait::redirectAfterLoginAction()`).
logoutNetteUser()  : void
makeBookendBlock()  : array<string, mixed>
Builds a compact "closed" block directly in `blocks` (same shape as {@see makeDayBlock()}, so `day.latte` renders it without a special branch). `$visualFromMin`/`$visualToMin` set the position (one hour), `$realFromMin`/`$realToMin` the label and click target (the real gap).
makeDayBlock()  : array<string, mixed>
normalizeSegments()  : array<int, array<string, mixed>>
normalizeWindows()  : array<int, array{from: string, to: string}>
Normalizes `windows` of the bridge {@see TimeSlotManager::dayDetail()} result, which is `array<int|string, mixed>` (it passed through the Service/Mapper/Repository layers without a type contract). They are not read with a bare cast but verified item by item (like `formatIntervals()`/`sumBusyMinutes()`). Shared by `renderDay()` and both `buildWeek*Data()` methods so the validation is not repeated in three places.
parseHm()  : int
Converts `H:MM`/`HH:MM` to minutes since midnight (`24:00` at the end of the day gives 1440, same convention as the Api).
prepareCalendarEntities()  : array<int, Calendar>
Returns the company calendars as entities (not `id => name` like {@see prepareCalendarOptions()}); the all-calendars week grid needs `getColor()`/`getName()`/`getId()` for the lane headers. Same filter and order as `prepareCalendarOptions()`.
prepareCalendarOptions()  : array<int, string>
Builds company calendar options `id => name`, active and not deleted only, ordered by `order` (the calendar management order). Unlike {@see prepareCompanyOptions()} there is no `asort()`: the `order` is intentional, not alphabetical, and the first one is the default (see {@see resolveOverviewCalendarId()}).
prepareCompanyOptions()  : array<int, NotTranslate>
Builds company options `id => internal name` (`Company.name` is a flat column, not translated).
processFailedLogin()  : void
processInvalidToken()  : void
processLogin()  : void
processLoginException()  : void
processMissingToken()  : void
processSuccessfulLogin()  : void
processValidToken()  : void
readLoginDeviceToken()  : string|null
Returns the raw known-device cookie for the Api login (the Api verifies its signature).
redirectAfterLoginAction()  : void
After a login attempt: a successful login returns to the request remembered before the login (`backlink`, {@see PrivilegeCheckTrait::loginReturnArgs()}), otherwise to the dashboard; a failed one stays on the login page and keeps the way back.
refreshNetteIdentity()  : bool
Refreshes the token of the same logged-in user: identity data (token, name, email) are overwritten in the stored object without `login()`, so neither the session id nor the form CSRF tokens change. The identity is stored in the session section as an object; the change is written at the end of the request.
rememberLoginDevice()  : void
After a successful login (password or OAuth) issues or renews the known-device cookie ({@see LoginDeviceCookie}): a signed proof that this browser has already logged in to the account. The next login sends it to the Api ({@see self::processLogin()}), and above the account limit the password from this browser is still verified, so foreign attempts do not lock out the owner. HttpOnly, SameSite=Lax, Secure on HTTPS; lifetime from `ratelimit.loginDevice.cookieLifetime`.
resolveContextCalendarId()  : int|null
Resolves the view calendar: the modal context wins (only a valid, active calendar of the company), otherwise GET (like {@see resolveContextCompanyId()}).
resolveContextCompanyId()  : int|null
Resolves the view company: the modal context wins, but only when it is a company the admin can see (otherwise a foreign or invalid id from a submitted form would override the selection), otherwise GET.
resolveFirstDayOfWeek()  : int
Returns the first day of the week for the current localization (ISO 8601: 1 = Monday .. 7 = Sunday).
resolveLoginErrorMessage()  : string
Resolves the flash message of a failed login.
resolveOverviewCalendarId()  : int|null
Resolves the calendar of the day overview from GET `calendarId`, otherwise the first calendar (`$calendarOptions` is already ordered by `order`, so first means default, the same decision as {@see \App\UI\Api\Company\Models\Managers\CalendarManager::resolveDefault()} in the Api).
resolveOverviewCompanyId()  : int|null
Resolves the company of the day overview from GET `companyId`, otherwise the first company by id.
resolveOverviewDate()  : string
Resolves the day/week overview date (`YYYY-MM-DD`) from GET `date`, otherwise today (like {@see resolveOverviewMonth()}).
resolveOverviewMonth()  : string
Resolves the overview month (`YYYY-MM`) from GET `month`, otherwise the current server month.
resolveOverviewYear()  : int
Resolves the year from GET `year` (`?year=2026`), otherwise the current one; the bounds keep years in a sane range.
resolveWeekAxis()  : array{0: int, 1: int}
Resolves the common hour axis of the week grid across all days (unlike the day timeline, where it is per day): the narrowest hour range covering the windows of all days that have any window.
revokeTokensOnLogout()  : void
Calls the Api logout (revokes the refresh family and deny-lists the access `jti`) for the tokens of the current session. Fail-open on purpose: any failure is only logged, because the local logout must not stay unfinished.
setNetteUser()  : void
shiftDate()  : string
Shifts a date by `$deltaDays` days (like {@see shiftMonth()}); an invalid date is returned unchanged.
shiftMonth()  : string
slotModalContext()  : array{companyId: int, calendarId: int, date: string, allCalendars: bool}|null
Returns the view context after a modal signal.
storeEditorUpload()  : bool
Stores a file; the target folder may not exist (first upload into the root), so after a failure the root is created once and the write is retried. Deeper subfolders are created by the editor user through `editorCreateFolder`.
sumBusyMinutes()  : int
Sums `to - from` (minutes) over the `list<TInterval>` `busy` array of the payload, without a cross-module import of the Api `TimeSlotManager` (same rule as {@see formatFreeMinutesLabel()}), see {@see parseHm()}.
tryRefresh()  : bool
Tries an auto-refresh for an expired access token. If the session holds a refresh token and the Api accepts it, stores the new pair in the session and returns true; otherwise returns false and the caller cancels the session.
weekdayLabel()  : string
Returns the full translated weekday name for an ISO weekday (1..7) via `WEEKDAY_KEYS`, `''` for invalid input (`dayOfWeek === 0`).
withPaddingDays()  : array<string, array<string, mixed>>
Adds padding days before/after the current month so the grid always starts on the `$firstDow`-th weekday (from the localization, {@see resolveFirstDayOfWeek()}) and ends on the day right before it. The `weekdayOrder` header columns must match the data. Returns a chronologically ordered array (leading padding, real days, trailing padding) keyed by date (`Y-m-d`); the template only renders. Empty input (no real data) is returned unchanged, because the template does not render a padding-only grid anyway.

Constants

Login page parameter with the key of the stored request to return to ({@see loginReturnArgs()}).

public string LOGIN_BACKLINK = 'backlink'

DEFAULT_ITEMS_PER_PAGE

Items per page of admin lists when the DB setting is missing.

protected mixed DEFAULT_ITEMS_PER_PAGE = 20

FORM_BACK_SUBSECTION

Session subsection mapping a key to the previous page URL of edit forms.

protected mixed FORM_BACK_SUBSECTION = 'formBack'

SESSION_NAMESPACE

protected mixed SESSION_NAMESPACE = 'admin'

BLOCKED_EXTENSIONS

Extensions that must not be uploaded into the web-served folder; otherwise an editor upload would mean running foreign code on the server. It is no content whitelist (the editor uploads what is needed), only a safeguard against executable files.

private mixed BLOCKED_EXTENSIONS = ['php', 'php3', 'php4', 'php5', 'php7', 'php8', 'phps', 'phtml', 'phar', 'cgi', 'pl', 'py', 'sh', 'bash', 'htaccess', 'htpasswd']

DAY_HOUR_HEIGHT_PX

Pixels per hour in the day timeline. IMPORTANT: must match `--cal-hour-height` (`abstracts/_vars.scss`); `day.latte` receives precomputed `axisHeightPx`/`topPx`/`heightPx`, so changing one side without the other misaligns the timeline.

private int DAY_HOUR_HEIGHT_PX = 72

EDITOR_PAGE_LIMIT

Listing cap; the dialog is a suggester, not a paged list.

private mixed EDITOR_PAGE_LIMIT = 500

EDITOR_PAGE_TARGETS

Routes the editor may link to: text pages only.

private mixed EDITOR_PAGE_TARGETS = ['front:system:page:show']

Other page routes need parameters the editor cannot know (Front:System:User:profile wants id and username, Front:System:Registration:registration wants email and activationKey, advert details want id/name). A link to them could not be built or would lead to a broken address, so they stay out of the offer.

Another target (e.g. Front:System:Page:homepage or Front:System:Page:form) is one more line here, provided its route needs only slug.

EDITOR_UPLOAD_FOLDER

Editor root (relative to the `uploads/` root). Changing it to `''` would open the whole `uploads/` tree to the manager; the editor user could then reach (and delete) files of other modules such as products and avatars.

private mixed EDITOR_UPLOAD_FOLDER = 'editorUploads'

IMAGE_EXTENSIONS

Extensions the file manager shows as a preview instead of a document icon.

private mixed IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'svg', 'bmp']

LOCALE_MAP

Maps the short language code to the full locale of the registered NEON sources.

private mixed LOCALE_MAP = ['cs' => 'cs_CZ', 'en' => 'en_US', 'sk' => 'sk_SK']

The short code (cs, en, sk) comes from LanguageContext, derived from the URL parameter loc/lang; the full locales are cs_CZ, en_US, sk_SK. translator.neon configures one shared fallback chain [cs_CZ, cs, en_US, en, sk_SK, sk] for all languages, so a short code would always map to cs_CZ first, whatever language is active. A direct match on the full locale bypasses that.

The Contributte LocaleResolvers\Router (the only configured resolver) looks for the route parameter locale, but this application routes under loc, so it always returns null and the translator falls back to default: cs. An explicit setLocale() from here bypasses the resolver, which Translator::getLocale() asks only while no locale has been set explicitly.

MINUTES_PER_DAY

Minutes per day, used for the closed bookend bars ({@see makeBookendBlock()}).

private int MINUTES_PER_DAY = 1440

MONTH_KEYS

ISO month (`m`, `01`..`12`) to key `list.overview.months.*`, see {@see formatMonthLabel()}; same mapping as {@see \App\UI\Front\Company\Components\BookingCalendarComponent\Traits\InitTrait::MONTH_KEYS}.

private array<string|int, mixed> MONTH_KEYS = ['01' => 'jan', '02' => 'feb', '03' => 'mar', '04' => 'apr', '05' => 'may', '06' => 'jun', '07' => 'jul', '08' => 'aug', '09' => 'sep', '10' => 'oct', '11' => 'nov', '12' => 'dec']

IMPORTANT: no @var array<string, string> here. PHP normalizes numeric string keys without a leading zero ('10'..'12') to int, so the real key type is int|string; the lookup self::MONTH_KEYS[$parsed->format('m')] still works because the access is normalized the same way.

WEEK_HOUR_WIDTH_PX

Pixels per hour in the week grid. IMPORTANT: must match `--cal-week-hour-width`.

private int WEEK_HOUR_WIDTH_PX = 108

WEEKDAY_KEYS

ISO weekday (`N`, 1..7) to key `list.overview.weekdays.*`, analog of {@see MONTH_KEYS} (the int-key trap does not apply, `N` has no leading zero).

private array<string|int, mixed> WEEKDAY_KEYS = [1 => 'mon', 2 => 'tue', 3 => 'wed', 4 => 'thu', 5 => 'fri', 6 => 'sat', 7 => 'sun']

Properties

$accessTokenValidator

Signature and expiry alone are not enough: a revoked token (`jti` on the deny-list) that has not expired yet would keep the session logged in although the Api guard rejects the same token. Same contract as {@see \App\Core\Traits\Api\Presenters\JwtAuthTrait::checkJwtAuth()}, one implementation.

public AccessTokenValidator $accessTokenValidator
Attributes
#[Inject]

$authUserManager

Admin `UserManager` for {@see UserManager::login()}. A module must not read through another module, so admin has its own `login()` that calls the same Api endpoint (`api/system/auth/login`).

public UserManager $authUserManager

The own property name avoids a collision with the generic $userManager from UserTrait (typed to the admin CRUD manager).

Attributes
#[Inject]

$columnSchema

Limits of the real database columns for {@see guardColumns()}.

public ColumnSchema $columnSchema
Attributes
#[Inject]

$editorAutosaveScope

Scope of the editor autosave drafts, see {@see \App\Core\Security\EditorAutosaveScope}.

public EditorAutosaveScope $editorAutosaveScope
Attributes
#[Inject]

$editorLinkPageManager

NOTE: own property name. `$pageManager` already exists on presenters (`PagePickerTrait`), and two `#[Inject]` properties of the same name would collide in the trait composition.

public PageManager $editorLinkPageManager
Attributes
#[Inject]

$editorUploadManager

NOTE: own property name, not `$fileSystemManager`.

public FileSystemManager $editorUploadManager

FileSystemPresenter takes the same manager as private readonly in its constructor, and PHP cannot redeclare a property of the same name between public and private.

Attributes
#[Inject]

$formSubmissionGuard

One-time submission tokens of "add" forms, see {@see guardFormSubmitOnce()}.

public FormSubmissionGuard $formSubmissionGuard
Attributes
#[Inject]

$loginDeviceCookie

Cookie of a known device for the password login throttle ({@see self::rememberLoginDevice()}).

public LoginDeviceCookie $loginDeviceCookie
Attributes
#[Inject]

$modalWindowFactory

Injected through Nette presenter inject (after construction), not the constructor.

public ModalWindowFactory $modalWindowFactory
Attributes
#[Inject]

$picker

Picker mode of a list; when not empty, the presenter renders its `show` grid as a selection modal (no add/edit/delete/checkboxes, each row has a select button) in the minimal `@picker.latte` layout.

public string $picker = ''

Opened by Picker; the value is the name of the opener's target field that receives the selection. Persistent, so it stays in grid links (filter/sort/paging) inside the modal. The presenter activates it in createComponent…Grid(): if ($this->picker !== '') { $grid->setPickerMode($this->picker); }.

Attributes
#[Persistent]

$pickerExclude

Id of the record that cannot be selected in picker mode (its select button is hidden).

public int $pickerExclude = 0

Serves self-referencing pickers (a parent page cannot be itself). Passed by Picker in the modal link via PickerConfig::targetParams; 0 means no exclusion.

Attributes
#[Persistent]

$pickerExcludeSubtreeLft

`lft`/`rgt` bounds of a whole subtree whose nodes cannot be selected in picker mode.

public int $pickerExcludeSubtreeLft = 0

Excludes the root and all descendants, so a node cannot be moved under its own descendant. 0 means no subtree exclusion.

Attributes
#[Persistent]

$pickerExcludeSubtreeRgt

public int $pickerExcludeSubtreeRgt = 0
Attributes
#[Persistent]

$queryCacheContext

Query cache policy for the whole admin request; `startup()` disables it.

public QueryCacheContext $queryCacheContext

Injected as a public property like the other admin base services; it cannot live in a trait because startup() touches it first.

Attributes
#[Inject]

$savedEntityId

Id of the record just saved; set by the `$processData` callback in `createForm()` after a successful save.

protected int|null $savedEntityId = null

The apply button (update) uses it: on the add action (new record, no id in the URL) it redirects to editing the new record (edit?id=...). Redirecting to this would lead back to an empty add form and the next apply would create a duplicate. When the callback leaves it null (e.g. a settings form without an edit action), the redirect goes to this.

$translatorDomain

protected string $translatorDomain = 'admin.company.presenters.schedule'

$occupiedSlotIds

Memoized set `timeSlotId => true` of slots with an active reservation (`pending`/`confirmed`), see {@see loadOccupiedSlotIds()}.

private array<int, true>|null $occupiedSlotIds = null

$userPrivileges

private array<int, string> $userPrivileges = []

privileges of the current user loaded in startup()

Methods

createComponent()

public createComponent(string $name) : IComponent|null
Parameters
$name : string
Return values
IComponent|null

flashFormOwnErrors()

Flashes form-level errors (`$form->getOwnErrors()`) as `danger` messages.

public flashFormOwnErrors(Form $form) : void

Called from onError of createForm(); public for component forms (AdminComponentFormTrait). Element errors are not flashed; {inputError} renders them at the field.

Parameters
$form : Form

flashRejectedUploads()

Flashes one `warning` listing the files the Api rejected (type, dimensions, unreadable) after a successful save of a form with a dropzone ({@see UploadRejection}).

public flashRejectedUploads(Result $result) : void

The save itself passed and accepted files are stored. The message is already translated (a list of names), so translate = false (see flashFormOwnErrors()). Call it before the redirect, because a flash sent through redirectUrl is lost; public for component forms.

Parameters
$result : Result

formatLayoutTemplateFiles()

Admin version of {@see \App\Core\Traits\Front\Presenters\ThemeInitTrait::formatLayoutTemplateFiles()}.

public formatLayoutTemplateFiles() : array<string|int, mixed>

It starts from AdminPage ($adminPageEntity, column layout), not from the Front $pageEntity, which an admin presenter does not have; admin also has no loggedLayout. The database value wins; getAdminLayout() is the fallback that LoginPresenter overrides with @login.latte.

Return values
array<string|int, mixed>

getContainer()

public getContainer() : Container
Return values
Container

getFormBackUrl()

Returns the stored previous page URL for the key, used by the `cancel` link in the template.

public getFormBackUrl(string|null $key) : string
Parameters
$key : string|null
Return values
string

guardFormSubmitOnce()

Protects a form against a double submit: one rendered form saves at most once.

public guardFormSubmitOnce(Form $form) : void

createForm() applies it to every form of an "add" action (isAddAction()), the component skeleton AdminComponentFormTrait likewise, and forms built outside both call it themselves (product import upload, credit notes, stocktake). A repeated submit of an edit form only saves the same values again, so edits are not guarded.

Adds the hidden token (FormSubmissionGuard::FIELD), issued on every GET render (on POST it comes back from the browser), and data-submit-once for submit-once.js, which blocks a second click in the browser. The server side (runSubmitOnce()) is the authority, because a request can come from outside the browser or the client script may not run. Idempotent.

Parameters
$form : Form

handleChangeLocale()

public handleChangeLocale(string $locale) : void
Parameters
$locale : string

handleEditorCreateFolder()

Creates a subfolder in `folder` (path relative to the editor root).

public handleEditorCreateFolder([string $folder = '' ][, string $name = '' ]) : void
Parameters
$folder : string = ''
$name : string = ''
Attributes
#[Requires]
$methods: 'POST'

handleEditorDelete()

Deletes a file or (recursively) a folder. An empty path would point to the editor root, which cannot be deleted.

public handleEditorDelete([string $path = '' ]) : void
Parameters
$path : string = ''
Attributes
#[Requires]
$methods: 'POST'

handleEditorFiles()

Sends the folder listing for the editor file manager. The `folder` parameter is a path relative to the editor root (empty means the root).

public handleEditorFiles([string $folder = '' ]) : void

Sends JSON {folder, parent, breadcrumb[], folders[], files[]}; paths in the response are again relative to the editor root, and a public URL is given for files only. The server path never leaves the server.

A non-existent folder gives an empty listing, not an error (until something is uploaded the root does not exist physically and the manager just shows the empty state).

Parameters
$folder : string = ''

handleEditorPages()

Sends the page list for the link dialog as JSON `{"pages": [{id, label, url}, ...]}` sorted by label.

public handleEditorPages() : void

Only text pages (EDITOR_PAGE_TARGETS); pages without a filled URL (href) are skipped too, because no link can be built for them.

handleEditorRename()

Renames a file or folder (`path` relative to the editor root).

public handleEditorRename([string $path = '' ][, string $name = '' ]) : void
Parameters
$path : string = ''
$name : string = ''
Attributes
#[Requires]
$methods: 'POST'

handleEditorUpload()

Accepts a file from the editor (field `file`, multipart POST; optional `folder` = subfolder of the editor root) and sends JSON `{"location": "/www/uploads/editorUploads/..."}`, the format TinyMCE expects from `images_upload_handler`.

public handleEditorUpload() : void
Attributes
#[Requires]
$methods: 'POST'

handleLogout()

public handleLogout() : void
Attributes
#[Requires]
$methods: 'POST'

handleMergeSlots()

Merges adjacent time slots (button "Merge" above the day timeline).

public handleMergeSlots(int $companyId, int $calendarId, string $date[, string $timeFrom = '' ][, string $timeTo = '' ]) : void

A presenter signal, not a component: unlike SlotModal there is no modal step, only an action bar over the checkbox selection. The logic lives in the Api ReservationManager::mergeSlots().

IMPORTANT: plain PRG (no AJAX/redrawControl). companyId/calendarId/date are not persistent parameters of the day view (read via getQuery(), see resolveContextCompanyId()), so redirect('this') would silently drop them; the target URL is built explicitly.

IMPORTANT: $timeFrom/$timeTo must have defaults. day.latte generates the base link without them (merge-slots.js appends them from the selected range), and {link mergeSlots! …} with a missing required parameter throws InvalidLinkException while rendering the template, so the signal is never reached. An empty string never arrives (the JS always overwrites it); if it did, mergeSlots() rejects it like any other invalid time.

Parameters
$companyId : int
$calendarId : int
$date : string
$timeFrom : string = ''
$timeTo : string = ''
Attributes
#[Requires]
$methods: 'POST'

handleSignOut()

public handleSignOut() : void
Attributes
#[Requires]
$methods: 'POST'

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
phpstan-assert-if-true

class-string $className

Return values
bool

initializeTranslator()

public initializeTranslator(string|null $alias) : void
Parameters
$alias : string|null

isAddAction()

Tells whether the current action creates a record: `add` and the `add<Something>` actions (`addMenu`, `addKey`). Derived from the server context only, like {@see formSubmitPrivilege()}.

public isAddAction() : bool
Return values
bool

isAllowed()

Returns whether the logged-in user has the given privilege.

public isAllowed(string $key) : bool

Reads the array loaded in startup(), with no database query. Fail-closed: returns false when loadUserPrivileges() was not called or failed.

Public so templates can call it ($presenter->isAllowed(...)) for conditional display of actions outside DataGrid (e.g. static menu management links).

Parameters
$key : string
Return values
bool

isSubmitOnceForm()

Tells whether the form was protected by {@see guardFormSubmitOnce()}.

public isSubmitOnceForm(Form $form) : bool
Parameters
$form : Form
Return values
bool

loginWithCredentials()

Logs in with username and password; called only by the login form (after validation and CSRF).

public loginWithCredentials(string $username, string $password[, bool $remember = false ]) : void

IMPORTANT: this must not be named handleLogin(). A handle* method is a public signal of every presenter (?do=login&username=...&password=...): the password could end up in the URL and the server log, and the form token would be bypassed. Nette finds signals by the handle prefix only.

Parameters
$username : string
$password : string
$remember : bool = false

publishFieldErrors()

Publishes element errors of a failed submit to the client.

public publishFieldErrors(Form $form) : void

IMPORTANT: field errors were visible only where the template has {inputError}; server rules on fields without it stopped the form but showed the user no message.

An AJAX submit sends payload.fieldErrors; a full render sets data-server-field-errors on <body> (see @layout.latte), which the script reads on load. The script avoids duplicating {inputError}.

Parameters
$form : Form

redirectToFormBack()

Redirects to the stored previous page and discards the key.

public redirectToFormBack(string|null $key) : void

Public for component forms (AdminComponentFormTrait), whose save submit calls it through $this->getPresenter().

Parameters
$key : string|null

renderDay()

Renders the day timeline of one calendar. Consumes the admin-only Api {@see \App\UI\Api\Company\Models\Managers\TimeSlotManager::dayDetailForCalendar()} (bridge {@see TimeSlotManager::dayDetail()}); gap filling into free/closed between windows is done by {@see buildDayTimeline()}. Without a calendar (the company has no active one) `dayDetail` stays `null` and the template shows a message instead of an empty card.

public renderDay() : void

renderShow()

`show` is the default action of admin presenters (fallback route).

public renderShow() : void

renderWeek()

Renders the week grid in two modes: one calendar (default) or all calendars of the company merged (`calendarId=0` in the URL; the switcher inside `week.latte` is a `<select>` with an extra "all calendars" option).

public renderWeek() : void

renderYear()

Year overview: previous/next year buttons, the year, and a table of months (January to December).

public renderYear() : void

The coarsest navigation level above show (month); every month links to its month overview.

IMPORTANT: the data is not fetched 12 times via the Api availabilityForCalendar() (a monthly payload with days, windows and free time; a year summary would cost twelve bridge requests per render). Two queries over Admin managers (buildYearMonths()) and aggregation in PHP are used instead, like the "Reserved" grid column (loadOccupiedSlotIds()) over the whole year. The year view therefore shows counts of reservations, blocks and closed days, not free or occupied minutes (a property of a day, not of a year).

resolveFormBackKey()

Returns the previous-page key: from the URL if present (survives the update redirect), otherwise on a plain GET render of the edit page stores `Referer` in the session under a new random key.

public resolveFormBackKey() : string

Public for component forms (AdminComponentFormTrait); a component reaches it through $this->getPresenter().

Return values
string

runSubmitOnce()

Runs a saving step of a form under its one-time token (a form without the token just runs it).

public runSubmitOnce(Form $form, string $backKey, callable(): bool $save) : bool|null
  • the first submit claims the token and saves; a failed save (false or an exception) releases it, so the corrected form can be sent again; a successful one keeps it with $savedEntityId. A redirect thrown from inside the step counts as a successful save, so the step should set $savedEntityId before redirecting;
  • a submit of an already used token does not run the step, flashes a notice, sets $savedEntityId to the record of the first submit and returns null; the caller leads where the first submit led;
  • a submit arriving while the first one still saves waits for it a few seconds, then behaves as above, or ends on the previous page with a warning (redirect);
  • a missing or malformed token does not run the step; the form gets a fresh token and a message and false is returned (the browser case is a form opened before the protection was deployed).

Public for component forms and form traits outside createForm().

Parameters
$form : Form
$backKey : string
$save : callable(): bool
Return values
bool|null —

true = saved now, false = not saved, null = saved by an earlier submit of the same form

setConfigParameters()

public setConfigParameters([bool $debugMode = false ][, bool $productionMode = false ]) : void
Parameters
$debugMode : bool = false
$productionMode : bool = false

setTranslator()

public setTranslator(ITranslator $translator) : $this
Parameters
$translator : ITranslator
Return values
$this

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
template
Return values
T

authorize()

Authorization hook; by default requires the `:Admin` privilege.

protected authorize() : void

LoginPresenter overrides it so the login page stays reachable for a guest.

authorizeFormSubmit()

Gate for writing presenter forms built by `createForm()`.

protected authorizeFormSubmit(Form $form) : void

IMPORTANT: a form submit is a signal (?do=editForm-submit) that Nette handles on any action, after action*(). A gate in actionAdd()/actionEdit() therefore covers only submits sent to add/edit; a submit sent to a list (show) passes authorizeListing() only, and a save callback without its own gate would write with view-only rights.

Called by createForm() before $processData (only for the update/save buttons) and by custom saving submits outside that gate.

Parameters
$form : Form

authorizeListing()

Gate for reading lists.

protected authorizeListing() : void

IMPORTANT: without this gate anyone inside the admin could open any list without the privilege. The gate lives here once, not in every presenter. The key is derived as in PrivilegeCheckTrait::checkCurrentPrivilege(): :Admin:<Module>:<Presenter>:show.

IMPORTANT: guards only the show action. Other actions have their own gates (add/edit/signals), and a blanket key would not fit them: dashboard, login, downloadPdf, statistics and similar have no ...:<action> key, so the fail-closed check would lock out even the administrator.

NOTE: every show screen needs its key in the privilege table and enabled for the admin role.

checkAdminAccess()

Entry gate to the admin: a guest is sent to login; a logged-in user without `:Admin` is sent to login with an error.

protected checkAdminAccess() : void

checkCurrentPrivilege()

Builds the key from the current request (`:Admin:<Module>:<Presenter>:<action>`) and checks it. `getName()` already contains the `Admin:...` prefix, so only one leading colon is added.

protected checkCurrentPrivilege() : void

Use it where the key matches the action; use explicit checkPrivilege() where it differs (another presenter, add versus edit).

checkPrivilege()

Checks a specific privilege; throws 403 when denied.

protected checkPrivilege(string $key) : void

IMPORTANT: a hidden button is no protection. A hand-built ?...&do=<signal> URL runs the action, so every non-standard action and every mutating signal needs the gate as its first statement; otherwise anyone who types the URL can run it.

IMPORTANT: form actions need two gates, one in the signal and one in process*() (the submit), because a submit arrives as its own request. With only the signal gate the submit stays open. See the WorkList traits.

Parameters
$key : string

createComponentSlotModal()

Slot edit modal. Templates do not mount it via `{control slotModal}`, they only generate `{link slotModal:open…!}` links; the content renders into the shared {@see \App\UI\Admin\System\Components\ModalWindowComponent\ModalWindow}.

protected createComponentSlotModal() : SlotModal
Return values
SlotModal

createForm()

Creates an edit form with the standard actions and a return to the previous page.

protected createForm(callable(ArrayHash<string|int, mixed>, Form): bool $processData) : Form

Buttons (the template renders them on the right as cancel, update, save):

  • save saves and returns to the previous page,
  • update saves and stays on the edit page (redirect to this),
  • cancel is only a link back, rendered by the template via getFormBackUrl().

On the first load of the edit page (plain GET render, not POST/signal) the previous page is taken from Referer, stored in the session under a random key, and the key goes into the hidden backKey field. It is per form so concurrent tabs do not overwrite each other, and it travels through the update redirect as a URL parameter.

The concrete presenter adds fields and defaults; this method provides the skeleton, buttons and redirects.

Parameters
$processData : callable(ArrayHash<string|int, mixed>, Form): bool

Saves the data; returns success (handles its own flash).

Return values
Form

domainTranslator()

Returns a translator prefixed with the presenter domain, or `null` (empty domain or a translator other than Contributte).

protected domainTranslator() : PrefixedTranslator|null

beforeRender() sets it only on $this->template. Templates created separately through createTemplate() (e.g. modal content built in the handle phase) do not have it, and their relative {_'...'} keys would print raw. Such places obtain it here.

Return values
PrefixedTranslator|null

formSubmitPrivilege()

Returns the privilege key required to save a form built by `createForm()`.

protected formSubmitPrivilege(Form $form) : string

Derived only from server context (presenter and action), never from a form field, so a changed hidden id cannot select a different right:

  • action add gives ...:add, action edit gives ...:edit;
  • any other action of a presenter with records (it has an add/edit action) gives ...:edit; a record form does not belong there, and whoever sends it needs at least the edit right;
  • a presenter without add/edit actions (single-page settings) gives the key of the current action, i.e. ...:show. Those screens have no other key in RBAC; an unknown action ends on a nonexistent key, which is a 403 (fail-closed).

A form bound to a custom action (sendEmail, inventory, addMenu...) overrides the method in its presenter and returns that action's key, always matched by form name so the exception does not let another form of the same presenter through.

Parameters
$form : Form
Return values
string

getAdminItemsPerPage()

Returns the default items per page of admin lists from the DB setting `admin.itemsPerPage` (module System = 1), falling back to {@see DEFAULT_ITEMS_PER_PAGE}.

protected getAdminItemsPerPage() : int
Return values
int

getAdminLayout()

Returns the layout name; in picker mode ({@see $picker}) the `show` grid uses the minimal `@picker.latte` (no sidebar/header) so the opener fetch returns only the table.

protected getAdminLayout() : string

LoginPresenter overrides it with @login.latte.

Return values
string

getFormBackFallback()

Returns the return URL when the previous page is unknown; override in a presenter without a `show` action.

protected getFormBackFallback() : string
Return values
string

getSessionKey()

Returns a session key string, e.g. 'front.auth.step1'.

protected getSessionKey([string|null $namespace = null ][, string|null $subsection = null ], string ...$extra) : string
Parameters
$namespace : string|null = null

Base namespace (module prefix). Uses SESSION_NAMESPACE if null.

$subsection : string|null = null

Subsection under the namespace. Uses SESSION_SUBSECTION constant if null.

$extra : string

Additional parts to append (e.g. 'step1', 'wizard', etc.).

Return values
string

getSessionSection()

Returns a Nette\SessionSection instance using a dynamically built session key.

protected getSessionSection([string|null $namespace = null ][, string|null $subsection = null ], string ...$extra) : SessionSection
Parameters
$namespace : string|null = null
$subsection : string|null = null
$extra : string
Tags
see
getSessionKey()
Return values
SessionSection

guardColumns()

Gates every text and number field of the form by the real column it writes to ({@see \App\Core\Forms\ColumnGate::guard()}): an oversized value is an error at the field, never a database error that closes the entity manager (admin walkthrough A1, A14).

protected guardColumns(Form $form, array<string, array<int, class-string>> $containers[, array<int, string> $except = [] ]) : void
Parameters
$form : Form
$containers : array<string, array<int, class-string>>

container path ('' = the form, a multiplier name = its rows) => entities

$except : array<int, string> = []

control names never gated (a value transformed before storage)

isSameSiteRequest()

Tells whether the request comes from the own site (Nette SameSite cookie); protects GET actions with a side effect.

protected isSameSiteRequest() : bool

getHttpRequest() returns the IRequest interface, while isSameSite() exists only on Nette\Http\Request.

Return values
bool

loadUserPrivileges()

Loads all privileges of the logged-in user with one database query. Call it right after `checkAdminAccess()` in `startup()` (the user is guaranteed to be logged in by then).

protected loadUserPrivileges() : void

setupTemplateTranslator()

Sets a translator prefixed with the presenter domain into the template (as Front components do, see {@see \App\Core\Base\BaseComponent::setup()}).

protected setupTemplateTranslator() : void

Templates then use short keys {_'key'} resolved as <translatorDomain>.key without {translator}. Shared keys in the layout are written absolutely with the // prefix, which bypasses the domain. The domain comes from the $translatorDomain property of the concrete presenter (admin.system.presenters.<presenter>).

buildDayTimeline()

Builds the day timeline blocks from the Api payload (`windows` + `segments`, both HH:MM): generic interval arithmetic, not the day resolution logic (that stays in the Api `dayDetailForCalendar()`).

private buildDayTimeline(array<int, array{from: string, to: string}> $windows, array<int, array<string, mixed>> $segments) : array{axisStartHour: int, axisEndHour: int, labelStartHour: int, labelEndHour: int, axisHeightPx: int, hourHeightPx: int, blocks: list>}

Returns a chronological list of blocks: free gaps inside windows, closed gaps between windows (for example a lunch break), and the segments unchanged. Every block gets topPx/heightPx (the axis starts at the full hour before the first window, DAY_HOUR_HEIGHT_PX per hour); the template only prints inline style. The totals axisHeightPx next to axisStartHour/axisEndHour exist so the height is computed once here, not duplicated in Latte.

Segments outside all windows are skipped (no window contains them, see the continue branch); findOutsideSegments(), called separately from renderDay(), picks them up.

The start and end of blocks get a compact clickable "closed" block (one DAY_HOUR_HEIGHT_PX, not proportional to the real gap), and axisStartHour/axisEndHour widen by that one hour. $labelStartHour/$labelEndHour keep numbered labels only for real hours (day.latte iterates range($labelStartHour, $labelEndHour) inclusively so the closing "17:00" shows). fromMin/ toMin of the block (click target and label) carry the real gap (0:00-8:00) even though it takes one hour visually.

A day without any window returns one all-day closed block (0:00-24:00) instead of nothing, see the comment in the body.

Parameters
$windows : array<int, array{from: string, to: string}>
$segments : array<int, array<string, mixed>>
Return values
array{axisStartHour: int, axisEndHour: int, labelStartHour: int, labelEndHour: int, axisHeightPx: int, hourHeightPx: int, blocks: list>}

buildMetaLabel()

Builds the metadata row of a day in the grid: "free · H:MM" (free), "occupied · H:MM" (partial/full, summed from the payload `busy` intervals, see {@see sumBusyMinutes()}) or an empty string (closed). Requires `freeMinutesLabel` already computed (see {@see decorateOverview()}).

private buildMetaLabel(array<string, mixed> $day) : string

IMPORTANT: partial/full is not always caused by a reservation. buildDays() also clips free time by lead time and the past, so today with elapsed hours can be partial with an empty busy array (0 minutes). "occupied · 0:00" would be nonsense, so such a day is shown as free with the real remaining free time.

Parameters
$day : array<string, mixed>
Return values
string

buildWeekAllData()

Builds week data of all company calendars merged: `numCalendars Ă— 7` bridge calls (small volumes, a company typically has a few calendars). A row per day holds `lanes`, one entry per calendar (`calendar` entity + its bubbles); the template builds nested `.cal-week__lane` rows.

private buildWeekAllData(array<int, Calendar> $calendars, array<int, string> $weekDates) : array{axisStartHour: int, axisEndHour: int, axisWidthPx: int, hourWidthPx: int, rows: list>}
Parameters
$calendars : array<int, Calendar>
$weekDates : array<int, string>
Return values
array{axisStartHour: int, axisEndHour: int, axisWidthPx: int, hourWidthPx: int, rows: list>}

buildWeekBubbles()

Converts day segments into week grid bubbles (`leftPx`/`widthPx` on the common week axis, {@see WEEK_HOUR_WIDTH_PX} instead of {@see DAY_HOUR_HEIGHT_PX}, because the week is horizontal).

private buildWeekBubbles(array<int, array<string, mixed>> $segments, int $axisStartHour, int $axisEndHour) : array<int, array<string, mixed>>

topPx/heightPx of the day timeline are not used. Segments outside the windows are intentionally not handled: only the day timeline lists them, the week grid has no per-day area to place such a segment.

Parameters
$segments : array<int, array<string, mixed>>
$axisStartHour : int
$axisEndHour : int
Return values
array<int, array<string, mixed>>

buildWeekDates()

Builds the 7 dates Monday to Sunday containing `$anyDateInWeek` (ISO `N` arithmetic, Monday = 1, same convention as {@see withPaddingDays()}). An invalid date falls back to today so the week is always built.

private buildWeekDates(string $anyDateInWeek) : array<int, string>
Parameters
$anyDateInWeek : string
Return values
array<int, string>

buildWeekSingleData()

Builds week data of one calendar: 7 bridge calls {@see TimeSlotManager::dayDetail()} (small volumes, same reasoning as {@see loadOccupiedSlotIds()}). Returns one row per day with bubbles on the common axis ({@see resolveWeekAxis()}).

private buildWeekSingleData(int $calendarId, array<int, string> $weekDates) : array{axisStartHour: int, axisEndHour: int, axisWidthPx: int, hourWidthPx: int, rows: list>}
Parameters
$calendarId : int
$weekDates : array<int, string>
Return values
array{axisStartHour: int, axisEndHour: int, axisWidthPx: int, hourWidthPx: int, rows: list>}

buildYearMonths()

Builds the twelve months of the year with a summary. Two queries over the whole year (calendar rows plus active reservations) and aggregation in PHP, with no Api call per month.

private buildYearMonths(int $calendarId, int $year) : array<int, array{month: string, label: string, isCurrent: bool, reservations: int, blocked: int, closedDays: int, hasAny: bool}>

NOTE: "reservations" counts slot rows with an active reservation (pending/confirmed), the same as the "Reserved" grid column and the day timeline. Spent carriers (public = 0 without an active reservation) are counted nowhere, because they are invisible in the calendar (see Api computeDay()).

Parameters
$calendarId : int
$year : int
Return values
array<int, array{month: string, label: string, isCurrent: bool, reservations: int, blocked: int, closedDays: int, hasAny: bool}>

createIdentity()

private createIdentity(array<string, mixed> $payload, string $token) : SimpleIdentity|null
Parameters
$payload : array<string, mixed>
$token : string
Return values
SimpleIdentity|null

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|null

createStandardComponent()

Creates a standard component (not a multiplier).

private createStandardComponent(string $name) : IComponent|null
Parameters
$name : string
Return values
IComponent|null

daySegmentKey()

private daySegmentKey(array<string, mixed> $segment) : string
Parameters
$segment : array<string, mixed>
Return values
string

decorateOverview()

Adds to every day of the Api payload {@see \App\UI\Api\Company\Models\Managers\TimeSlotManager::availabilityForCalendar()} a readable `freeMinutesLabel` (`H:MM`) and a uniform `metaLabel` ("free · H:MM" / "occupied · H:MM", empty for `closed`), so the template only prints. Also adds padding days of the neighboring months ({@see withPaddingDays()}) so the grid always starts on the `$firstDow`-th weekday and ends on the day before it.

private decorateOverview(array<int|string, mixed> $overview, int $firstDow) : array<int|string, mixed>
Parameters
$overview : array<int|string, mixed>
$firstDow : int

ISO first day of the week from the localization (1..7), see resolveFirstDayOfWeek()

Return values
array<int|string, mixed>

editorBreadcrumb()

Builds the breadcrumb navigation: cumulative segment paths.

private editorBreadcrumb(string $relative) : array<int, array{label: string, path: string}>
Parameters
$relative : string
Return values
array<int, array{label: string, path: string}>

editorLinkLangId()

Returns the language used to pick labels and URLs (default 1, Czech).

private editorLinkLangId() : int
Return values
int

editorPageLabel()

Returns the page label: title, otherwise name, otherwise `#id`.

private editorPageLabel(Page $page, int $langId) : string
Parameters
$page : Page
$langId : int
Return values
string

editorPageRoute()

Returns the first page route the editor may link to. A page may have several (many-to-many); the whitelisted one is taken, not the first in order, so a page with both `Page:show` and `Page:form` ends up on the right one.

private editorPageRoute(Page $page) : Route|null
Parameters
$page : Page
Return values
Route|null

editorPageUrl()

Returns the front end address of a page. The router builds it from the page route, so `/o-nas` and any prefix come out on their own.

private editorPageUrl(Page $page, int $langId) : string|null
Parameters
$page : Page
$langId : int
Return values
string|null —

null when the page is not a text page (see EDITOR_PAGE_TARGETS), has no filled URL, or its route cannot build a link

editorParentFolder()

Returns the parent folder (null in the editor root).

private editorParentFolder(string $relative) : string|null
Parameters
$relative : string
Return values
string|null

editorPath()

Converts a client path (relative to the editor root) to an Api path (relative to the `uploads/` root).

private editorPath(string $relative) : string|null

This is the only place where the prefix is added, so the segment check lives here too. The Api rejects .. as well, but without this check a ../ from the request would not escape uploads/ yet would escape editorUploads/ into neighboring folders of other modules.

Parameters
$relative : string
Return values
string|null —

null when the path contains a forbidden segment

editorUploadName()

Builds a safe and unique file name. Sanitization copies the Api layer rules (webalize of the base and of the extension), so the name returned in the URL is exactly the name the file is stored under. The random suffix prevents collisions: `writeFile()` overwrites the same name without asking, so two different `obrazek.jpg` files would overwrite each other.

private editorUploadName(string $untrustedName) : string|null
Parameters
$untrustedName : string
Return values
string|null —

null for a forbidden extension or an unusable name

editorUploadUrl()

Returns the public URL of a file. `$path` is relative to the `uploads/` root (so it includes `editorUploads/`), the same shape as other uploads in the system.

private editorUploadUrl(string $path) : string
Parameters
$path : string
Return values
string

ensureEditorRoot()

Creates the editor root folder; when it already exists the Api returns false and nothing happens.

private ensureEditorRoot() : void

expireIdleLogin()

Returns whether the login expired from inactivity. If so, revokes the tokens (like a logout), clears the `auth` section, logs out the Nette identity and returns true; otherwise moves the activity stamp forward.

private expireIdleLogin(AuthSessionStorage $authStorage) : bool

Admin keeps 30 min, or 8 h with its own "remember me", regardless of the front end "remember me" (the auth section is shared, its 31 days do not count here). An expiry in admin logs out of the front end too (one session).

Parameters
$authStorage : AuthSessionStorage
Return values
bool

findOutsideSegments()

Returns the segments that did not land in any block of {@see buildDayTimeline()}: outside all offered windows (a reservation from before the working hours were narrowed or before a day override, or a block on a `closed_day`). Without this the admin would not know about such a reservation.

private findOutsideSegments(array<int, array<string, mixed>> $segments, array<int, array<string, mixed>> $blocks) : array<int, array<string, mixed>>

Compared by the composite key (type|from|to|timeSlotId), not the bare timeSlotId, which can theoretically be null (see the findDayRowsRaw() guard) and would collide between segments. makeDayBlock() keeps the segment from/to/timeSlotId via array_merge() next to fromLabel/toLabel; the keys do not overlap.

Parameters
$segments : array<int, array<string, mixed>>
$blocks : array<int, array<string, mixed>>
Return values
array<int, array<string, mixed>>

formatDayLabel()

Builds a readable day label for the day timeline header ("Thursday 12. March 2026"). The weekday name uses its own key `list.overview.weekdays.*` in this presenter domain, see {@see WEEKDAY_KEYS}; not a cross-domain `//` to `calendar.form.openingHours.days.*`, because those are abbreviations for working hours and this is a full name for a header date.

private formatDayLabel(string $date) : string

NOTE: the month is in the nominative (reuses MONTH_KEYS/months.* like formatMonthLabel()), not the genitive; grammatically imprecise in Czech but readable, and it avoids a new translation domain for a single heading.

Parameters
$date : string
Return values
string

formatFreeMinutesLabel()

Converts minutes to `H:MM` (like Api `TimeSlotManager::formatMinutes()`, without a cross-module import).

private formatFreeMinutesLabel(int $minutes) : string
Parameters
$minutes : int
Return values
string

formatIntervals()

Formats Api payload intervals (`[{from, to}, …]`) as `09:00–10:00, 12:00–13:00`; empty input gives an empty string and the template then renders no badge. The payload is `mixed` (it came over the bridge), so every item is verified, with no bare casts.

private formatIntervals(mixed $intervals) : string
Parameters
$intervals : mixed
Return values
string

formatMonthLabel()

Builds a readable month label ("March 2026"). The project has no general Latte filter for a localized month name (PHP `date('F')` is English), so this follows the Front {@see \App\UI\Front\Company\Components\BookingCalendarComponent\Traits\InitTrait} (ISO month to `months.*` key, translated via the translator) within this admin presenter, with no cross-module import of a Front component.

private formatMonthLabel(string $month) : string
Parameters
$month : string
Return values
string

formatWeekRangeLabel()

Builds the week range label ("10.–16. March 2026" within one month; otherwise "24. August – 30. August 2026", with the year when that differs too). Readability, not canvas-exact typography.

private formatWeekRangeLabel(array<int, string> $weekDates) : string
Parameters
$weekDates : array<int, string>

7 items, see buildWeekDates()

Return values
string

initializeMetaTags()

Fills the template with the title, meta tags and HTML text of the page from the translation (`PageText`) of the current language, for the layout (like the Front {@see \App\Core\Traits\Front\Presenters\PageInitTrait}).

private initializeMetaTags(AdminPage|null $adminPageEntity) : void

When the page or translation is missing, the values are empty strings and the layout uses its fallbacks.

Parameters
$adminPageEntity : AdminPage|null

loadOccupiedSlotIds()

Returns the set of slots with an active reservation (`pending`/`confirmed`). Loads all active reservations of the Admin {@see ReservationManager} in one query (small volumes, no query per day or month). Memoized in a property; only {@see buildYearMonths()} calls it.

private loadOccupiedSlotIds() : array<int, true>

NOTE: intentionally duplicated in TimeSlotPresenter::loadOccupiedSlotIds() after the presenters were split; each has its own scope (grid column there, year summary here).

Return values
array<int, true>

localizationCurrencyKey()

Admin ignores the localization currency (it takes the language only).

private localizationCurrencyKey(object $localization) : string|null
Parameters
$localization : object
Return values
string|null

loginReturnArgs()

Remembers the request of a logged-out user so the login can return to it ({@see storeRequest()}, `backlink` of the login page, restored by `AuthStorageTrait::redirectAfterLoginAction()`).

private loginReturnArgs() : array<string, string>

Without it a save sent after a logout in another tab (or after the idle timeout) ended on the login page with no explanation, the login led to the dashboard and the filled-in form was lost. A restored POST renders the form again with the submitted values, but Nette does not process it (Request::RESTORED, no blind replay): the admin checks the values and saves once more. An AJAX request is not stored (naja would replay it as a page). The user id stored with the request is the previous identity, so a different account logging in is not sent there.

Return values
array<string, string>

makeBookendBlock()

Builds a compact "closed" block directly in `blocks` (same shape as {@see makeDayBlock()}, so `day.latte` renders it without a special branch). `$visualFromMin`/`$visualToMin` set the position (one hour), `$realFromMin`/`$realToMin` the label and click target (the real gap).

private makeBookendBlock(int $visualFromMin, int $visualToMin, int $realFromMin, int $realToMin, int $axisStartMin) : array<string, mixed>
Parameters
$visualFromMin : int
$visualToMin : int
$realFromMin : int
$realToMin : int
$axisStartMin : int
Return values
array<string, mixed>

makeDayBlock()

private makeDayBlock(string $type, int $fromMin, int $toMin, int $axisStartMin, array<string, mixed>|null $segment) : array<string, mixed>
Parameters
$type : string
$fromMin : int
$toMin : int
$axisStartMin : int
$segment : array<string, mixed>|null
Return values
array<string, mixed>

normalizeSegments()

private normalizeSegments(mixed $segments) : array<int, array<string, mixed>>
Parameters
$segments : mixed
Return values
array<int, array<string, mixed>>

normalizeWindows()

Normalizes `windows` of the bridge {@see TimeSlotManager::dayDetail()} result, which is `array<int|string, mixed>` (it passed through the Service/Mapper/Repository layers without a type contract). They are not read with a bare cast but verified item by item (like `formatIntervals()`/`sumBusyMinutes()`). Shared by `renderDay()` and both `buildWeek*Data()` methods so the validation is not repeated in three places.

private normalizeWindows(mixed $windows) : array<int, array{from: string, to: string}>
Parameters
$windows : mixed
Return values
array<int, array{from: string, to: string}>

parseHm()

Converts `H:MM`/`HH:MM` to minutes since midnight (`24:00` at the end of the day gives 1440, same convention as the Api).

private parseHm(string $hm) : int
Parameters
$hm : string
Return values
int

prepareCalendarEntities()

Returns the company calendars as entities (not `id => name` like {@see prepareCalendarOptions()}); the all-calendars week grid needs `getColor()`/`getName()`/`getId()` for the lane headers. Same filter and order as `prepareCalendarOptions()`.

private prepareCalendarEntities(int $companyId) : array<int, Calendar>
Parameters
$companyId : int
Return values
array<int, Calendar>

prepareCalendarOptions()

Builds company calendar options `id => name`, active and not deleted only, ordered by `order` (the calendar management order). Unlike {@see prepareCompanyOptions()} there is no `asort()`: the `order` is intentional, not alphabetical, and the first one is the default (see {@see resolveOverviewCalendarId()}).

private prepareCalendarOptions(int $companyId) : array<int, string>
Parameters
$companyId : int
Return values
array<int, string>

prepareCompanyOptions()

Builds company options `id => internal name` (`Company.name` is a flat column, not translated).

private prepareCompanyOptions() : array<int, NotTranslate>

Shared by all calendar views (year/month/week/day) as the company picker above the calendar.

NOTE: intentionally duplicated in TimeSlotPresenter::prepareCompanyOptions() after the presenters were split; each has its own scope (grid and form there, calendar here).

Return values
array<int, NotTranslate>

processLogin()

private processLogin(string $username, string $password[, bool $remember = false ]) : void
Parameters
$username : string
$password : string
$remember : bool = false

processLoginException()

private processLoginException(Exception $e) : void
Parameters
$e : Exception

readLoginDeviceToken()

Returns the raw known-device cookie for the Api login (the Api verifies its signature).

private readLoginDeviceToken() : string|null
Return values
string|null

redirectAfterLoginAction()

After a login attempt: a successful login returns to the request remembered before the login (`backlink`, {@see PrivilegeCheckTrait::loginReturnArgs()}), otherwise to the dashboard; a failed one stays on the login page and keeps the way back.

private redirectAfterLoginAction() : void

refreshNetteIdentity()

Refreshes the token of the same logged-in user: identity data (token, name, email) are overwritten in the stored object without `login()`, so neither the session id nor the form CSRF tokens change. The identity is stored in the session section as an object; the change is written at the end of the request.

private refreshNetteIdentity(array<string, mixed> $payload, string $token) : bool
Parameters
$payload : array<string, mixed>
$token : string
Return values
bool —

false when impossible (nobody logged in or another user); the caller uses login

rememberLoginDevice()

After a successful login (password or OAuth) issues or renews the known-device cookie ({@see LoginDeviceCookie}): a signed proof that this browser has already logged in to the account. The next login sends it to the Api ({@see self::processLogin()}), and above the account limit the password from this browser is still verified, so foreign attempts do not lock out the owner. HttpOnly, SameSite=Lax, Secure on HTTPS; lifetime from `ratelimit.loginDevice.cookieLifetime`.

private rememberLoginDevice(mixed $payload) : void
Parameters
$payload : mixed

resolveContextCalendarId()

Resolves the view calendar: the modal context wins (only a valid, active calendar of the company), otherwise GET (like {@see resolveContextCompanyId()}).

private resolveContextCalendarId(array{companyId: int, calendarId: int, date: string, allCalendars: bool}|null $context, array<int, string> $calendarOptions) : int|null
Parameters
$context : array{companyId: int, calendarId: int, date: string, allCalendars: bool}|null
$calendarOptions : array<int, string>
Return values
int|null

resolveContextCompanyId()

Resolves the view company: the modal context wins, but only when it is a company the admin can see (otherwise a foreign or invalid id from a submitted form would override the selection), otherwise GET.

private resolveContextCompanyId(array{companyId: int, calendarId: int, date: string, allCalendars: bool}|null $context, array<int, string|Stringable> $companyOptions) : int|null
Parameters
$context : array{companyId: int, calendarId: int, date: string, allCalendars: bool}|null
$companyOptions : array<int, string|Stringable>
Return values
int|null

resolveFirstDayOfWeek()

Returns the first day of the week for the current localization (ISO 8601: 1 = Monday .. 7 = Sunday).

private resolveFirstDayOfWeek() : int

The month grid and its header derive from it (withPaddingDays(), weekdayOrder in renderShow()).

The type guard checks the shared mapped superclass Localization, not the System entity of another module (LocalizationContext::getLocalization() returns ?object; this avoids a cross-module import, and the shared ancestor is common to the Admin/Api/Front branches).

Without a localization (context not populated) or with an out-of-range value it falls back to 1 (Monday), the previous grid behavior.

Return values
int

resolveLoginErrorMessage()

Resolves the flash message of a failed login.

private resolveLoginErrorMessage(Result $result) : string

IMPORTANT: a brute-force throttle must not be shown as "wrong password"; the limit would then look like an inexplicably failing form.

results.rateLimitExceeded (the generic text of ResultCode) carries no seconds and does not belong to login. The login flow therefore has its own key in the same family as admin.general.login.error.message: login.rateLimit.message, with the seconds passed as %count% (Contributte: numeric 2nd argument).

The Api sends retryAfter in data (AuthPresenter::rateLimitedResult()); the internal bridge call does not see the Retry-After header.

Parameters
$result : Result
Return values
string

resolveOverviewCalendarId()

Resolves the calendar of the day overview from GET `calendarId`, otherwise the first calendar (`$calendarOptions` is already ordered by `order`, so first means default, the same decision as {@see \App\UI\Api\Company\Models\Managers\CalendarManager::resolveDefault()} in the Api).

private resolveOverviewCalendarId(array<int, string> $calendarOptions) : int|null

null means the company has no active calendar (the Api TimeSlotManager scopes by calendar); the template then shows a readable message, not an empty grid.

Parameters
$calendarOptions : array<int, string>
Return values
int|null

resolveOverviewCompanyId()

Resolves the company of the day overview from GET `companyId`, otherwise the first company by id.

private resolveOverviewCompanyId(array<int, string|Stringable> $companyOptions) : int|null

null means no company in the system (nothing to show).

Parameters
$companyOptions : array<int, string|Stringable>
Return values
int|null

resolveOverviewDate()

Resolves the day/week overview date (`YYYY-MM-DD`) from GET `date`, otherwise today (like {@see resolveOverviewMonth()}).

private resolveOverviewDate() : string
Return values
string

resolveOverviewMonth()

Resolves the overview month (`YYYY-MM`) from GET `month`, otherwise the current server month.

private resolveOverviewMonth() : string
Return values
string

resolveOverviewYear()

Resolves the year from GET `year` (`?year=2026`), otherwise the current one; the bounds keep years in a sane range.

private resolveOverviewYear() : int
Return values
int

resolveWeekAxis()

Resolves the common hour axis of the week grid across all days (unlike the day timeline, where it is per day): the narrowest hour range covering the windows of all days that have any window.

private resolveWeekAxis(iterable<string|int, mixed> $details) : array{0: int, 1: int}

A day without windows (closed) does not widen the axis.

Parameters
$details : iterable<string|int, mixed>

day detail payloads (dayDetail() outputs; the bridge returns array<int|string, mixed>, so every item is verified with is_array())

Return values
array{0: int, 1: int} —

[axisStartHour, axisEndHour], [0, 0] when no day has a window

revokeTokensOnLogout()

Calls the Api logout (revokes the refresh family and deny-lists the access `jti`) for the tokens of the current session. Fail-open on purpose: any failure is only logged, because the local logout must not stay unfinished.

private revokeTokensOnLogout(AuthSessionStorage $authStorage, mixed $userId) : void
Parameters
$authStorage : AuthSessionStorage
$userId : mixed

setNetteUser()

private setNetteUser(array<string, mixed>|null $payload, string $token) : void
Parameters
$payload : array<string, mixed>|null
$token : string

shiftDate()

Shifts a date by `$deltaDays` days (like {@see shiftMonth()}); an invalid date is returned unchanged.

private shiftDate(string $date, int $deltaDays) : string
Parameters
$date : string
$deltaDays : int
Return values
string

shiftMonth()

private shiftMonth(string $month, int $delta) : string
Parameters
$month : string
$delta : int
Return values
string

slotModalContext()

Returns the view context after a modal signal.

private slotModalContext() : array{companyId: int, calendarId: int, date: string, allCalendars: bool}|null

IMPORTANT: the component signal link does not carry the page query parameters (companyId/calendarId/date), because Nette prefixes all arguments with the component name. Without the explicit context renderDay()/renderWeek() would fall back to defaults after a block click and redraw a completely different day. The component therefore receives the context as signal parameters and it is read back here. null (plain GET) means nothing changed.

Return values
array{companyId: int, calendarId: int, date: string, allCalendars: bool}|null

storeEditorUpload()

Stores a file; the target folder may not exist (first upload into the root), so after a failure the root is created once and the write is retried. Deeper subfolders are created by the editor user through `editorCreateFolder`.

private storeEditorUpload(string $folder, string $name, string $base64) : bool
Parameters
$folder : string
$name : string
$base64 : string
Return values
bool

sumBusyMinutes()

Sums `to - from` (minutes) over the `list<TInterval>` `busy` array of the payload, without a cross-module import of the Api `TimeSlotManager` (same rule as {@see formatFreeMinutesLabel()}), see {@see parseHm()}.

private sumBusyMinutes(mixed $intervals) : int
Parameters
$intervals : mixed
Return values
int

tryRefresh()

Tries an auto-refresh for an expired access token. If the session holds a refresh token and the Api accepts it, stores the new pair in the session and returns true; otherwise returns false and the caller cancels the session.

private tryRefresh(AuthSessionStorage $authStorage) : bool
Parameters
$authStorage : AuthSessionStorage
Return values
bool

weekdayLabel()

Returns the full translated weekday name for an ISO weekday (1..7) via `WEEKDAY_KEYS`, `''` for invalid input (`dayOfWeek === 0`).

private weekdayLabel(int $dayOfWeek) : string
Parameters
$dayOfWeek : int
Return values
string

withPaddingDays()

Adds padding days before/after the current month so the grid always starts on the `$firstDow`-th weekday (from the localization, {@see resolveFirstDayOfWeek()}) and ends on the day right before it. The `weekdayOrder` header columns must match the data. Returns a chronologically ordered array (leading padding, real days, trailing padding) keyed by date (`Y-m-d`); the template only renders. Empty input (no real data) is returned unchanged, because the template does not render a padding-only grid anyway.

private withPaddingDays(array<string, array<string, mixed>> $days, string $month, int $firstDow) : array<string, array<string, mixed>>
Parameters
$days : array<string, array<string, mixed>>
$month : string
$firstDow : int

ISO first day of the week (1 = Monday .. 7 = Sunday)

Return values
array<string, array<string, mixed>>
On this page

Search results