HandleTrait
Signals of the grid.
inlineUpdate!changes the value of an inline renderer (InlineEditableRenderer: select and switch), validates it against the options and stores it viamanager->updateColumns().relationToggle!toggles a join table link (RelationColumn and RelationTogglable). The cell is not a row column but a flag from a related collection picked byscope(e.g. the RBAC matrix of role by privilege).delete!is the built-in row delete (aRowActionwithsetSignal('delete')). In a tree grid it deletes the whole subtree viamanager->deleteNode()and closes the lft/rgt gap (with the scope filter, see TreeTrait::deleteTreeNode()).moveUp!andmoveDown!move a record among its siblings in a tree; the nested-set recompute runs in the model layer, see TreeTrait.dragDrop!moves a node in the sort view with a neighbor contract (AJAX JSON).
IMPORTANT: all writing signals (inlineUpdate, relationToggle, delete, moveUp,
moveDown, dragDrop, sortChildren) have #[Requires(methods: 'POST')]; a GET link could be
triggered by a robot or a prefetch inside a logged-in session. The client calls them by POST
(datagrid.js, tree-drag.js, row actions are <button formaction> in
RowAction::isPost()); GridWritingSignalsRequirePostTest guards this.
Tree state (expanding, focus) is handled by signals in TreeStateTrait.
Signals end with a redirect to this (post-redirect-get); see the methods for AJAX branches.
Table of Contents
Properties
- $explainedDeleteFailures : array<int, true>
- Delete result codes whose message from the `results` domain is shown to the admin instead of the generic failure message, see {@see explainDeleteFailures()}.
- $explainedUpdateFailures : array<int, true>
- Inline update (toggle, select) result codes whose message from the `results` domain is shown instead of the generic failure, see {@see explainUpdateFailures()}.
Methods
- explainDeleteFailures() : self
- Opts in to showing domain reasons for a rejected delete to the admin (e.g. a job cannot be deleted because it contains invoiced work sheets).
- explainUpdateFailures() : self
- Opts in to showing the domain reason of a refused inline update (e.g. the only default PDF layout cannot be turned off). Same rules as {@see explainDeleteFailures()}: each code needs a `results.<camelCase>` translation in cs/en/sk, generic codes do not belong here.
- handleDelete() : void
- handleDragDrop() : void
- Moves a node in the tree by drag and drop, changing the parent and the position among siblings.
- handleInlineUpdate() : void
- handleMoveDown() : void
- handleMoveUp() : void
- handleRelationToggle() : void
- Toggles a join table link, a {@see RelationColumn} cell (a flag from a related collection picked by `scope`, e.g. a role in the RBAC matrix).
- confirmDelete() : bool
- Server-side half of the delete confirmation: a delete runs only with the confirmation flag ({@see DataGrid::CONFIRM_FIELD}) in the POST.
- deleteFlashType() : string
- Returns the flash color for {@see deleteResultKey()}; an explained success with a caveat carries the level of its code.
- deleteResultKey() : string
- Returns the flash key for a delete result.
- flattenPost() : array<int, array{0: string, 1: string}>
- Flattens POST data into `name => value` pairs for hidden fields (`ids[]` and nested arrays keep their bracket names); the confirmation flag itself is left out.
- isExplainedDeleteOutcome() : bool
- moveNode() : void
- Moves a node up or down by delegating to the manager model layer ({@see \App\Core\Traits\Shared\Models\Managers\TreeTrait}). The tree configuration (lft/rgt/parent properties and scope) comes from {@see \App\UI\Admin\System\Components\DataGridComponent\DataGrid::buildTreeConfig()}.
- moveResultKey() : string
- Returns the flash key of a move result. A rejected contract (`TREE_STALE`) and a lock (`TREE_LOCKED`) are not plain failures: the user should know to reload or wait a moment.
- requireDeletePrivilege() : void
- IMPORTANT: server-side gate for bulk delete, key `…:del`.
- requireEditPrivilege() : void
- IMPORTANT: server-side gate for writes in the grid: inline editing, relation toggles and tree moves.
- requireGroupActionPrivilege() : void
- Gate for any group action; the action declares its own privilege ({@see GroupAction::setPrivilege()}), `delete` has it built in.
- requireOfferedDeletion() : void
- IMPORTANT: server-side delete gate; responds 403 when the grid does not offer deletion in this request.
- rowDeleteConfirmText() : string|null
- Confirmation text of the row delete action (the same the modal shows).
- updateResultKey() : string
- Returns the flash key for an inline update result; an explained refusal carries the message of its code (absolute key into the `results` domain).
Properties
$explainedDeleteFailures
Delete result codes whose message from the `results` domain is shown to the admin instead of the generic failure message, see {@see explainDeleteFailures()}.
private
array<int, true>
$explainedDeleteFailures
= []
ResultCode->value => true
$explainedUpdateFailures
Inline update (toggle, select) result codes whose message from the `results` domain is shown instead of the generic failure, see {@see explainUpdateFailures()}.
private
array<int, true>
$explainedUpdateFailures
= []
ResultCode->value => true
Methods
explainDeleteFailures()
Opts in to showing domain reasons for a rejected delete to the admin (e.g. a job cannot be deleted because it contains invoiced work sheets).
public
explainDeleteFailures(ResultCode ...$codes) : self
Each code needs a translation results.<camelCase> in cs/en/sk. Without the call the grid
shows the generic message. Generic codes (database or API error) do not belong here, their
text tells the admin nothing. It also applies to a success with a caveat (e.g.
PRODUCT_DELETE_HIDDEN, where records were only hidden): the message of the code is shown in
the color of its ResultCode::level() instead of the plain success message.
Parameters
- $codes : ResultCode
Return values
selfexplainUpdateFailures()
Opts in to showing the domain reason of a refused inline update (e.g. the only default PDF layout cannot be turned off). Same rules as {@see explainDeleteFailures()}: each code needs a `results.<camelCase>` translation in cs/en/sk, generic codes do not belong here.
public
explainUpdateFailures(ResultCode ...$codes) : self
Parameters
- $codes : ResultCode
Return values
selfhandleDelete()
public
handleDelete(int $id) : void
Parameters
- $id : int
Attributes
- #[Requires]
- $methods: 'POST'
handleDragDrop()
Moves a node in the tree by drag and drop, changing the parent and the position among siblings.
public
handleDragDrop(int $dragId, int|null $newParentId, int $afterId[, int|null $oldParentId = null ][, int|null $prevId = null ][, int|null $nextId = null ]) : void
afterId: -1 inserts as the first child of newParent, 0 as the last child, >0 after the
sibling with that id.
Neighbor contract: when the client sends prevId/nextId (and oldParentId, 0 for a
root), the server verifies under the tree lock that the node is still under oldParentId
and that prevId/nextId are the current neighbors under newParentId. The position is
then derived from prevId and afterId is ignored: prevId -1 means the start, 0 the end
without neighbor verification (collapsed target), >0 after that node; nextId 0 means
nothing after it, >0 before that node. Without them afterId applies.
The AJAX response is {success: true} or {success: false, code: '<ResultCode name>'}
(TREE_STALE when the tree changed meanwhile, TREE_LOCKED on a concurrent operation);
the JS shows a message by the code and reloads the list. Non-AJAX uses a redirect with a flash.
Calls TreeTrait::moveSubtreeTo().
Parameters
- $dragId : int
- $newParentId : int|null
- $afterId : int
- $oldParentId : int|null = null
- $prevId : int|null = null
- $nextId : int|null = null
Attributes
- #[Requires]
- $methods: 'POST'
handleInlineUpdate()
public
handleInlineUpdate(int $id, string $column, string $value) : void
Parameters
- $id : int
- $column : string
- $value : string
Attributes
- #[Requires]
- $methods: 'POST'
handleMoveDown()
public
handleMoveDown(int $id) : void
Parameters
- $id : int
Attributes
- #[Requires]
- $methods: 'POST'
handleMoveUp()
public
handleMoveUp(int $id) : void
Parameters
- $id : int
Attributes
- #[Requires]
- $methods: 'POST'
handleRelationToggle()
Toggles a join table link, a {@see RelationColumn} cell (a flag from a related collection picked by `scope`, e.g. a role in the RBAC matrix).
public
handleRelationToggle(int $id, string $scope, string $column, string $value) : void
The value cannot be stored via updateColumns() (it would write a row column, not the join
row with a composite key), so it delegates to a data source implementing RelationTogglable.
Parameters
- $id : int
- $scope : string
- $column : string
- $value : string
Attributes
- #[Requires]
- $methods: 'POST'
confirmDelete()
Server-side half of the delete confirmation: a delete runs only with the confirmation flag ({@see DataGrid::CONFIRM_FIELD}) in the POST.
private
confirmDelete(string|null $message, array<int, int> $ids[, string|null $actionLabel = null ]) : bool
The modal (datagrid.js) sets the flag after "Delete". Before the bundle initialized (a large
page still loading), without JavaScript, or from a hand-built request, the button submitted the
delete with no question at all (admin walkthrough A18). Without the flag nothing is deleted; the
grid renders a confirmation page instead ("Do you really want to delete these records?"), which
repeats the same request with the flag. An AJAX request gets a warning and a redirect (the modal
never sends one unconfirmed).
Parameters
- $message : string|null
- $ids : array<int, int>
- $actionLabel : string|null = null
-
label of a non-delete bulk action that asks first (null = delete)
Return values
bool —true = confirmed, run it; false = the confirmation page renders instead
deleteFlashType()
Returns the flash color for {@see deleteResultKey()}; an explained success with a caveat carries the level of its code.
private
deleteFlashType(Result $result) : string
Parameters
- $result : Result
Return values
stringdeleteResultKey()
Returns the flash key for a delete result.
private
deleteResultKey(Result $result) : string
A permission denial and a database failure get different messages, so the admin can tell whether to fix permissions or report an error. The bridge preserves the result code.
Parameters
- $result : Result
Return values
stringflattenPost()
Flattens POST data into `name => value` pairs for hidden fields (`ids[]` and nested arrays keep their bracket names); the confirmation flag itself is left out.
private
static flattenPost(array<string|int, mixed> $data[, string $prefix = '' ]) : array<int, array{0: string, 1: string}>
Parameters
- $data : array<string|int, mixed>
- $prefix : string = ''
Return values
array<int, array{0: string, 1: string}>isExplainedDeleteOutcome()
private
isExplainedDeleteOutcome(ResultCode|null $code) : bool
Parameters
- $code : ResultCode|null
Return values
boolmoveNode()
Moves a node up or down by delegating to the manager model layer ({@see \App\Core\Traits\Shared\Models\Managers\TreeTrait}). The tree configuration (lft/rgt/parent properties and scope) comes from {@see \App\UI\Admin\System\Components\DataGridComponent\DataGrid::buildTreeConfig()}.
private
moveNode(int $id, bool $up) : void
Parameters
- $id : int
- $up : bool
moveResultKey()
Returns the flash key of a move result. A rejected contract (`TREE_STALE`) and a lock (`TREE_LOCKED`) are not plain failures: the user should know to reload or wait a moment.
private
moveResultKey(Result $result) : string
tree-drag.js reads the same texts from grid data attributes (AJAX branch).
Parameters
- $result : Result
Return values
stringrequireDeletePrivilege()
IMPORTANT: server-side gate for bulk delete, key `…:del`.
private
requireDeletePrivilege() : void
Without it a logged-in user without the delete right could delete in bulk, because the
registered action and CSRF protect only against a foreign site. Every screen with bulk
delete must have the key :Admin:<Module>:<Presenter>:del allowed for the admin role,
otherwise this fail-closed gate would lock even the administrator out.
NOTE: outside the admin branch (a grid in another module, a component without a presenter) it is not enforced, as in requireEditPrivilege(); the derived key would not exist there and would lock the action for everyone.
requireEditPrivilege()
IMPORTANT: server-side gate for writes in the grid: inline editing, relation toggles and tree moves.
private
requireEditPrivilege() : void
These signals changed data without asking for any right, so anyone inside the admin could
flip any switch in any grid by a hand-built URL. The input validations (column exists, is
inline editable, value is in options) check the shape of the input, not permission.
The key is derived from the attached presenter as :Admin:<Module>:<Presenter>:edit, by the
same rule as PrivilegeCheckTrait::checkCurrentPrivilege()
and the add button in DataGrid::setAddLink().
Inline editing is editing, so edit fits, and no per-grid registration is needed. Every
screen with inline, relation or tree writes must have the edit key allowed for admin.
NOTE: when the key cannot be derived (a grid outside an admin presenter) the gate lets the
call through. Failing closed would break a foreign context that does not follow the admin
naming convention; the real gate remains checkAdminAccess() in startup(), which everyone
has to pass.
requireGroupActionPrivilege()
Gate for any group action; the action declares its own privilege ({@see GroupAction::setPrivilege()}), `delete` has it built in.
private
requireGroupActionPrivilege(GroupAction $action) : void
NOTE: it mirrors the condition the button is rendered by; whoever sees the action must pass, and vice versa.
Parameters
- $action : GroupAction
requireOfferedDeletion()
IMPORTANT: server-side delete gate; responds 403 when the grid does not offer deletion in this request.
private
requireOfferedDeletion() : void
Presenters add the delete action conditionally:
if ($this->isAllowed(':Admin:System:UserGroup:del')) {
$grid->addRowAction(RowAction::create('delete')->setSignal('delete'));
}
A hidden button is no protection: without this gate the delete! signal could be called by
a hand-built URL even without the right. The gate has no key of its own; it mirrors the
condition the UI already uses (the component is rebuilt in the signal request too), so it
cannot lock out anyone who sees the button. The presenter condition is therefore
authoritative: to tighten a grid, wrap addRowAction() and addGroupAction() in isAllowed().
NOTE: per-row visibility (RowAction::setVisibleWhen()) is not handled here; it mirrors
entity state (e.g. a closed work sheet) and the model guards it in beforeDelete().
rowDeleteConfirmText()
Confirmation text of the row delete action (the same the modal shows).
private
rowDeleteConfirmText() : string|null
Return values
string|nullupdateResultKey()
Returns the flash key for an inline update result; an explained refusal carries the message of its code (absolute key into the `results` domain).
private
updateResultKey(Result $result) : string
Parameters
- $result : Result