SchedulePresenter
extends BasePresenter
in package
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_BACKLINK
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]
$adminPageManager
public
AdminPageManager
$adminPageManager
Attributes
- #[Inject]
$authManager
public
AuthManager
$authManager
Attributes
- #[Inject]
$authStorageManager
public
AuthStorageManager
$authStorageManager
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]
$container
public
Container
$container
Attributes
- #[Inject]
$deviceDetector
public
DeviceDetector
$deviceDetector
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]
$eventDispatcher
public
EventDispatcher
$eventDispatcher
Attributes
- #[Inject]
$formSubmissionGuard
One-time submission tokens of "add" forms, see {@see guardFormSubmitOnce()}.
public
FormSubmissionGuard
$formSubmissionGuard
Attributes
- #[Inject]
$jwtManager
public
JwtManager
$jwtManager
Attributes
- #[Inject]
$languageContext
public
LanguageContext
$languageContext
Attributes
- #[Inject]
$languageManager
public
LanguageManager
$languageManager
Attributes
- #[Inject]
$localizationContext
public
LocalizationContext
$localizationContext
Attributes
- #[Inject]
$localizationManager
public
LocalizationManager
$localizationManager
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]
$presenterContext
public
PresenterContext
$presenterContext
Attributes
- #[Inject]
$privilegeManager
public
PrivilegeManager
$privilegeManager
Attributes
- #[Inject]
$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]
$routerDispatcher
public
RouterDispatcher
$routerDispatcher
Tags
$settingContext
public
SettingContext
$settingContext
Attributes
- #[Inject]
$settingManager
public
SettingManager
$settingManager
Attributes
- #[Inject]
$themeContext
public
ThemeContext
$themeContext
Attributes
- #[Inject]
$themeManager
public
ThemeManager
$themeManager
Attributes
- #[Inject]
$themeResolver
public
DefaultThemeResolver
$themeResolver
Attributes
- #[Inject]
$translator
public
ITranslator
$translator
Tags
$translatorSessionResolver
public
Session
$translatorSessionResolver
Tags
$userManager
public
UserManager
$userManager
Attributes
- #[Inject]
$adminPageEntity
protected
AdminPage|null
$adminPageEntity
= null
$debugMode
protected
bool
$debugMode
= false
$productionMode
protected
bool
$productionMode
= false
$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'
$calendarManager read-only
private
CalendarManager
$calendarManager
$companyManager read-only
private
CompanyManager
$companyManager
$isLoggingIn
private
bool
$isLoggingIn
= false
$occupiedSlotIds
Memoized set `timeSlotId => true` of slots with an active reservation (`pending`/`confirmed`), see {@see loadOccupiedSlotIds()}.
private
array<int, true>|null
$occupiedSlotIds
= null
$reservationManager read-only
private
ReservationManager
$reservationManager
$slotModalFactory read-only
private
SlotModalFactory
$slotModalFactory
$theme
private
Theme|null
$theme
= null
$timeSlotManager read-only
private
TimeSlotManager
$timeSlotManager
$userPrivileges
private
array<int, string>
$userPrivileges
= []
privileges of the current user loaded in startup()
Methods
__construct()
public
__construct(TimeSlotManager $timeSlotManager, CompanyManager $companyManager, ReservationManager $reservationManager, CalendarManager $calendarManager, SlotModalFactory $slotModalFactory) : mixed
Parameters
- $timeSlotManager : TimeSlotManager
- $companyManager : CompanyManager
- $reservationManager : ReservationManager
- $calendarManager : CalendarManager
- $slotModalFactory : SlotModalFactory
actionDay()
public
actionDay() : void
actionWeek()
public
actionWeek() : void
actionYear()
public
actionYear() : void
beforeRender()
public
beforeRender() : void
createComponent()
public
createComponent(string $name) : IComponent|null
Parameters
- $name : string
Return values
IComponent|nullflashFormOwnErrors()
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>getAdminPageEntity()
public
getAdminPageEntity() : AdminPage|null
Return values
AdminPage|nullgetContainer()
public
getContainer() : Container
Return values
ContainergetFormBackUrl()
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
stringgetModalWindow()
public
getModalWindow() : ModalWindow
Return values
ModalWindowgetRouterDispatcher()
public
getRouterDispatcher() : RouterDispatcher
Return values
RouterDispatchergetTranslator()
public
getTranslator() : ITranslator
Return values
ITranslatorguardFormSubmitOnce()
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
Return values
boolinitializeTranslator()
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
boolisAllowed()
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
boolisSubmitOnceForm()
Tells whether the form was protected by {@see guardFormSubmitOnce()}.
public
isSubmitOnceForm(Form $form) : bool
Parameters
- $form : Form
Return values
boolloginWithCredentials()
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
stringrunSubmitOnce()
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 (
falseor 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$savedEntityIdbefore redirecting; - a submit of an already used token does not run the step, flashes a notice, sets
$savedEntityIdto the record of the first submit and returnsnull; 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
falseis 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
$thisstartup()
public
startup() : void
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
Tauthorize()
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
checkAuthentication()
protected
checkAuthentication() : 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
createComponentModalWindow()
protected
createComponentModalWindow() : ModalWindow
Return values
ModalWindowcreateComponentSlotModal()
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
SlotModalcreateForm()
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):
savesaves and returns to the previous page,updatesaves and stays on the edit page (redirect tothis),cancelis 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
FormdomainTranslator()
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|nullformSubmitPrivilege()
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
addgives...:add, actioneditgives...: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
stringgetAdminItemsPerPage()
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
intgetAdminLayout()
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
stringgetFormBackFallback()
Returns the return URL when the previous page is unknown; override in a presenter without a `show` action.
protected
getFormBackFallback() : string
Return values
stringgetSessionKey()
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
stringgetSessionSection()
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
Return values
SessionSectionguardColumns()
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)
initializePresenterContext()
protected
initializePresenterContext() : void
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
boolloadUserPrivileges()
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
logout()
protected
logout() : void
setTranslatorDomain()
protected
setTranslatorDomain() : 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: listbuildMetaLabel()
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
stringbuildWeekAllData()
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: listbuildWeekBubbles()
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: listbuildYearMonths()
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|nullcreateMultiplierComponent()
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|nulldaySegmentKey()
private
daySegmentKey(array<string, mixed> $segment) : string
Parameters
- $segment : array<string, mixed>
Return values
stringdecorateOverview()
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
inteditorPageLabel()
Returns the page label: title, otherwise name, otherwise `#id`.
private
editorPageLabel(Page $page, int $langId) : string
Parameters
- $page : Page
- $langId : int
Return values
stringeditorPageRoute()
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|nulleditorPageUrl()
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|nulleditorPath()
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
stringensureEditorRoot()
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
boolfindOutsideSegments()
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
stringformatFreeMinutesLabel()
Converts minutes to `H:MM` (like Api `TimeSlotManager::formatMinutes()`, without a cross-module import).
private
formatFreeMinutesLabel(int $minutes) : string
Parameters
- $minutes : int
Return values
stringformatIntervals()
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
stringformatMonthLabel()
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
stringformatWeekRangeLabel()
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
stringgetClientIp()
private
getClientIp() : string
Return values
stringinitializeMetaTags()
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
initializePage()
private
initializePage() : void
isDevelopment()
private
isDevelopment() : bool
Return values
boolloadLocalization()
private
loadLocalization() : Localization|null
Return values
Localization|nullloadOccupiedSlotIds()
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|nullloginReturnArgs()
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>logoutNetteUser()
private
logoutNetteUser() : void
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
intprepareCalendarEntities()
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>processFailedLogin()
private
processFailedLogin(AuthSessionStorage $authStorage, Result $result, string $username) : void
Parameters
- $authStorage : AuthSessionStorage
- $result : Result
- $username : string
processInvalidToken()
private
processInvalidToken(AuthStorageInterface $authStorage) : void
Parameters
- $authStorage : AuthStorageInterface
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
processMissingToken()
private
processMissingToken() : void
processSuccessfulLogin()
private
processSuccessfulLogin(AuthSessionStorage $authStorage, Result $result, string $username[, bool $remember = false ]) : void
Parameters
- $authStorage : AuthSessionStorage
- $result : Result
- $username : string
- $remember : bool = false
processValidToken()
private
processValidToken(AuthSessionStorage $authStorage, string $token) : void
Parameters
- $authStorage : AuthSessionStorage
- $token : string
readLoginDeviceToken()
Returns the raw known-device cookie for the Api login (the Api verifies its signature).
private
readLoginDeviceToken() : string|null
Return values
string|nullredirectAfterLoginAction()
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|nullresolveContextCompanyId()
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|nullresolveFirstDayOfWeek()
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
intresolveLoginErrorMessage()
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
stringresolveOverviewCalendarId()
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|nullresolveOverviewCompanyId()
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|nullresolveOverviewDate()
Resolves the day/week overview date (`YYYY-MM-DD`) from GET `date`, otherwise today (like {@see resolveOverviewMonth()}).
private
resolveOverviewDate() : string
Return values
stringresolveOverviewMonth()
Resolves the overview month (`YYYY-MM`) from GET `month`, otherwise the current server month.
private
resolveOverviewMonth() : string
Return values
stringresolveOverviewYear()
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
intresolveWeekAxis()
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 returnsarray<int|string, mixed>, so every item is verified withis_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
stringshiftMonth()
private
shiftMonth(string $month, int $delta) : string
Parameters
- $month : string
- $delta : int
Return values
stringslotModalContext()
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}|nullstoreEditorUpload()
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
boolsumBusyMinutes()
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
inttryRefresh()
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
boolweekdayLabel()
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
stringwithPaddingDays()
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)