CoolCMS API

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 via manager->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 by scope (e.g. the RBAC matrix of role by privilege).
  • delete! is the built-in row delete (a RowAction with setSignal('delete')). In a tree grid it deletes the whole subtree via manager->deleteNode() and closes the lft/rgt gap (with the scope filter, see TreeTrait::deleteTreeNode()).
  • moveUp! and moveDown! 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
self

explainUpdateFailures()

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
self

handleDelete()

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
string

deleteResultKey()

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
string

flattenPost()

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

moveNode()

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
string

requireDeletePrivilege()

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

updateResultKey()

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
Return values
string
On this page

Search results