1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Data\Model;
6:
7: use Atk4\Core\Exception as CoreException;
8: use Atk4\Core\Factory;
9: use Atk4\Data\Exception;
10: use Atk4\Data\Model;
11:
12: trait UserActionsTrait
13: {
14: /** @var array<mixed> The seed used by addUserAction() method. */
15: protected $_defaultSeedUserAction = [UserAction::class];
16:
17: /** @var array<string, UserAction> Collection of user actions - using key as action system name */
18: protected $userActions = [];
19:
20: /**
21: * Register new user action for this model. By default UI will allow users to trigger actions
22: * from UI.
23: *
24: * @template T of Model
25: *
26: * @param array<mixed>|\Closure(T, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed): mixed $seed
27: */
28: public function addUserAction(string $name, $seed = []): UserAction
29: {
30: $this->assertIsModel();
31:
32: if ($seed instanceof \Closure) {
33: $seed = ['callback' => $seed];
34: }
35:
36: $seed = Factory::mergeSeeds($seed, $this->_defaultSeedUserAction);
37: $action = UserAction::fromSeed($seed);
38: $this->_addIntoCollection($name, $action, 'userActions');
39:
40: return $action;
41: }
42:
43: /**
44: * Returns true if user action with a corresponding name exists.
45: */
46: public function hasUserAction(string $name): bool
47: {
48: if ($this->isEntity() && $this->getModel()->hasUserAction($name)) {
49: return true;
50: }
51:
52: return $this->_hasInCollection($name, 'userActions');
53: }
54:
55: private function addUserActionFromModel(string $name, UserAction $action): void
56: {
57: $this->assertIsEntity();
58: $action->getOwner()->assertIsModel(); // @phpstan-ignore-line
59:
60: // clone action and store it in entity
61: $action = clone $action;
62: $action->unsetOwner();
63: $this->_addIntoCollection($name, $action, 'userActions');
64: }
65:
66: /**
67: * Returns list of actions for this model. Can filter actions by records they apply to.
68: * It will also skip system user actions (where system === true).
69: *
70: * @param string $appliesTo e.g. UserAction::APPLIES_TO_ALL_RECORDS
71: *
72: * @return array<string, UserAction>
73: */
74: public function getUserActions(string $appliesTo = null): array
75: {
76: $this->assertIsModel();
77:
78: return array_filter($this->userActions, static function (UserAction $action) use ($appliesTo) {
79: return !$action->system && ($appliesTo === null || $action->appliesTo === $appliesTo);
80: });
81: }
82:
83: /**
84: * Returns one action object of this model. If action not defined, then throws exception.
85: */
86: public function getUserAction(string $name): UserAction
87: {
88: if ($this->isEntity() && !$this->_hasInCollection($name, 'userActions') && $this->getModel()->hasUserAction($name)) {
89: $this->addUserActionFromModel($name, $this->getModel()->getUserAction($name));
90: }
91:
92: try {
93: return $this->_getFromCollection($name, 'userActions');
94: } catch (CoreException $e) {
95: throw (new Exception('User action is not defined'))
96: ->addMoreInfo('model', $this)
97: ->addMoreInfo('userAction', $name);
98: }
99: }
100:
101: /**
102: * Remove specified action.
103: *
104: * @return $this
105: */
106: public function removeUserAction(string $name)
107: {
108: $this->assertIsModel();
109:
110: $this->_removeFromCollection($name, 'userActions');
111:
112: return $this;
113: }
114:
115: /**
116: * Execute specified action with specified arguments.
117: *
118: * @param mixed ...$args
119: *
120: * @return mixed
121: */
122: public function executeUserAction(string $name, ...$args)
123: {
124: return $this->getUserAction($name)->execute(...$args);
125: }
126:
127: protected function initUserActions(): void
128: {
129: // declare our basic CRUD actions for the model
130: $this->addUserAction('add', [
131: 'fields' => true,
132: 'modifier' => UserAction::MODIFIER_CREATE,
133: 'appliesTo' => UserAction::APPLIES_TO_NO_RECORDS,
134: 'callback' => 'save',
135: 'description' => 'Add ' . $this->getModelCaption(),
136: ]);
137:
138: $this->addUserAction('edit', [
139: 'fields' => true,
140: 'modifier' => UserAction::MODIFIER_UPDATE,
141: 'appliesTo' => UserAction::APPLIES_TO_SINGLE_RECORD,
142: 'callback' => static function (Model $entity) {
143: $entity->assertIsLoaded();
144:
145: return $entity->save();
146: },
147: ]);
148:
149: $this->addUserAction('delete', [
150: 'appliesTo' => UserAction::APPLIES_TO_SINGLE_RECORD,
151: 'modifier' => UserAction::MODIFIER_DELETE,
152: 'callback' => static function (Model $entity) {
153: return $entity->delete();
154: },
155: ]);
156:
157: $this->addUserAction('validate', [
158: // 'appliesTo' => any entity!
159: 'description' => 'Provided with modified values will validate them but will not save',
160: 'modifier' => UserAction::MODIFIER_READ,
161: 'fields' => true,
162: 'system' => true, // don't show by default
163: 'args' => [
164: 'intent' => ['type' => 'string'],
165: ],
166: ]);
167: }
168: }
169: