-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathReadData.php
More file actions
333 lines (301 loc) · 11.7 KB
/
ReadData.php
File metadata and controls
333 lines (301 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
<?php
namespace exface\Core\Actions;
use exface\Core\CommonLogic\ActionInputValidator;
use exface\Core\Exceptions\Actions\ActionTaskInvalidException;
use exface\Core\Interfaces\Actions\iReadData;
use exface\Core\CommonLogic\AbstractAction;
use exface\Core\Interfaces\DataSheets\DataSheetInterface;
use exface\Core\Interfaces\Tasks\TaskInterface;
use exface\Core\Interfaces\DataSources\DataTransactionInterface;
use exface\Core\Interfaces\Tasks\ResultInterface;
use exface\Core\Factories\ResultFactory;
use exface\Core\Interfaces\WidgetInterface;
use exface\Core\Interfaces\Widgets\iUseInputWidget;
use exface\Core\Exceptions\Actions\ActionRuntimeError;
use exface\Core\Interfaces\Widgets\iSupportLazyLoading;
use exface\Core\CommonLogic\UxonObject;
use exface\Core\Factories\WidgetFactory;
use exface\Core\Interfaces\Widgets\iHaveColumns;
/**
* Reads data required for its widget or explicitly requestend in the config of the action.
*
* This action reads fresh data from the data source of its object.
*
* There are multiple scenarios where this action can be used:
*
* - Reading data for a lazy loading widget. This is done automatically by all lazy loading widgets. In
* this case, no explicit configuration for the action is required.
* - Reading data for an explicitly defined widget. This is similar to the automatics of lazy loading
* widgets, but you set the `widget_to_read_for` explicitly.
* - Reading arbitrary data - e.g. in `ActionChains`. In this case, the action will read fresh data
* for the data sheet it receives as input. You can also explicitly define `columns` to read in the
* configuration of the action.
*
* @author Andrej Kabachnik
*
*/
class ReadData extends AbstractAction implements iReadData
{
private $update_filter_context = null;
private $widgetToReadFor = null;
private ?UxonObject $customColumnsUxon = null;
private ?UxonObject $customSortersUxon = null;
/**
* @inheritDoc
*/
protected function validateApplicability(ActionInputValidator $validator): void
{
parent::validateApplicability($validator);
$expectedColumns = $validator->getExpectedColumns();
try {
$validator->validateTaskColumns($expectedColumns);
} catch (ActionTaskInvalidException $exception) {
$task = $validator->getTask();
if(!$task->hasInputData()) {
throw $exception;
}
// We ignore unexpected columns IF they are system columns.
$inputData = $task->getInputData();
foreach ($exception->getIssue(ActionTaskInvalidException::ISSUE_UNEXPECTED_COLUMN) as $badColumn) {
$col = $inputData->getColumns()->get($badColumn);
if(
$col !== null &&
$col->isAttribute() &&
$col->getAttribute()->isSystem()
) {
$inputData->getColumns()->removeByKey($badColumn);
}
}
}
}
/**
*
* {@inheritDoc}
* @see \exface\Core\CommonLogic\AbstractAction::perform()
*/
protected function perform(TaskInterface $task, DataTransactionInterface $transaction) : ResultInterface
{
$data_sheet = $this->getInputDataSheet($task);
$data_sheet->removeRows();
$dataWidget = $this->getWidgetToReadFor($task);
// If reading for a specific widget, ask the widget, what columns it needs.
// Note: there may also be cases, where data is read for another object - e.g. if the ReadData
// action is part of an action chain. In this case, simply read the columns there are.
if ($dataWidget !== null && ($dataWidget instanceof iSupportLazyLoading) && $this === $dataWidget->getLazyLoadingAction()) {
$data_sheet = $dataWidget->prepareDataSheetToRead($data_sheet);
}
if ($data_sheet->getColumns()->isEmpty(false)) {
throw new ActionRuntimeError($this, 'Cannot read data for ' . $data_sheet->getMetaObject() . ' - no columns to read specified!');
}
// Read from the data source
$affected_rows = $data_sheet->dataRead();
// Replace the filter conditions in the current window context by the ones in this data sheet
// It is important to do it after the data had been read, because otherwise the newly set
// context filters would affect the result of the read operation (context filters are automatically
// applied to the query, each time, data is fetched)
if ($this->getUpdateFilterContext($data_sheet)) {
$this->updateFilterContext($data_sheet);
}
$result = ResultFactory::createDataResult($task, $data_sheet);
if (null !== $message = $this->getResultMessageText()) {
$message = str_replace('%number%', $affected_rows, $message);
} else {
$message = $this->getWorkbench()->getCoreApp()->getTranslator()->translate('ACTION.READDATA.RESULT', ['%number%' => $affected_rows], $affected_rows);
}
$result->setMessage($message);
return $result;
}
/**
*
* @param DataSheetInterface $data_sheet
* @return \exface\Core\Actions\ReadData
*/
protected function updateFilterContext(DataSheetInterface $data_sheet)
{
$context = $this->getApp()->getWorkbench()->getContext()->getScopeWindow()->getFilterContext();
$context->removeConditionsForObject($data_sheet->getMetaObject());
foreach ($data_sheet->getFilters()->getConditions() as $condition) {
if (! $condition->isEmpty()){
$context->addCondition($condition);
}
}
return $this;
}
/**
*
* @return bool
*/
public function getUpdateFilterContext(DataSheetInterface $data) : bool
{
return $this->update_filter_context ?? ! $data->hasAggregations();
}
/**
* Set to TRUE/FALSE to force passing the filters of this action to the filter context (or not).
*
* By default, any explicit read-operation (not autosuggest or so) without
* aggregation will update the filter context
*
* @uxon-property update_filter_context
* @uxon-type boolean
*
* @param bool $value
* @return \exface\Core\Actions\ReadData
*/
public function setUpdateFilterContext(bool $value) : ReadData
{
$this->update_filter_context = $value;
return $this;
}
/**
* The id of the widget to read the data for.
*
* If not set, the input widget, of the trigger of the task will be used.
*
* Setting a custom target widget allows to create buttons, that load/refresh data
* in a specific widget.
*
* @uxon-property widget_id_to_read_for
* @uxon-type string
*
* @param string $value
* @return ReadData
*/
public function setWidgetIdToReadFor(string $value) : ReadData
{
$this->widgetToReadFor = $value;
return $this;
}
/**
* Returns the widget for which the data is to be read.
*
* @param TaskInterface $task
*
* @return WidgetInterface|NULL
*/
public function getWidgetToReadFor(TaskInterface $task) : ?WidgetInterface
{
if ($this->widgetToReadFor !== null) {
$widget = $task->getPageTriggeredOn()->getWidget($this->widgetToReadFor);
} else {
if ($task->isTriggeredByWidget()) {
$trigger = $task->getWidgetTriggeredBy();
} elseif ($this->isDefinedInWidget()) {
$trigger = $this->getWidgetDefinedIn();
}
if ($trigger !== null) {
if ($trigger instanceof iUseInputWidget) {
$widget = $trigger->getInputWidget();
} else {
$widget = $trigger;
}
}
}
$widget = $this->applyCustomWidgetProperties($task, $widget);
return $widget;
}
/**
*
* @return UxonObject|NULL
*/
protected function getCustomColumnsUxon() : ?UxonObject
{
return $this->customColumnsUxon;
}
/**
* Explicitly specify the column to be read
*
* By default this action will read the columns of its input widget. However, you can also define your
* own set of columns here explicitly. In this case, only the filters will be inherited from the input
* widget.
*
* @uxon-property columns
* @uxon-type \exface\Core\Widgets\DataColumn[]|\exface\Core\Widgets\DataColumnGroup[]
* @uxon-template [{"attribute_alias": ""}]
*
* @param UxonObject $value
* @return ReadData
*/
protected function setColumns(UxonObject $value) : ReadData
{
$this->customColumnsUxon = $value;
return $this;
}
/**
*
* @return bool
*/
protected function hasCustomColumns() : bool
{
return $this->customColumnsUxon !== null;
}
/**
*
* @return UxonObject|NULL
*/
protected function getCustomSortersUxon() : ?UxonObject
{
return $this->customSortersUxon;
}
/**
* Explicitly specify sorting for this read operation
*
* @uxon-property sorters
* @uxon-type \exface\Core\CommonLogic\DataSheets\DataSorter
* @uxon-template [{"attribute_alias": "", "directions": "ASC"}]
*
* @param UxonObject $value
* @return ReadData
*/
protected function setSorters(UxonObject $value) : ReadData
{
$this->customSortersUxon = $value;
return $this;
}
/**
*
* @return bool
*/
protected function hasCustomSorters() : bool
{
return $this->customSortersUxon !== null;
}
/**
* Modifies the given base widget adding `columns` and other properties explicitly defined in this action
*
* @return WidgetInterface|NULL
*/
protected function applyCustomWidgetProperties(TaskInterface $task, WidgetInterface $baseWidget = null) : ?WidgetInterface
{
$uxonForCols = $this->getCustomColumnsUxon();
$uxonForSorters = $this->getCustomSortersUxon();
if ($uxonForCols === null && $uxonForSorters === null) {
return $baseWidget;
}
// Use the input widget if the object of the exported data is the same as
// that of the widget or inherits from it (= if we know, that the data sheet
// can read all the attributes required).
// That is, is we have a `LOCATION` and a `FACTORY`, that extends `LOCATION`,
// we can prefill a LOCATION-widget with FACTORY-data, but not the other way
// around.
if ($baseWidget !== null && ($baseWidget instanceof iHaveColumns) && $this->getMetaObject()->is($baseWidget->getMetaObject())) {
$page = $baseWidget->getPage();
$uxon = $baseWidget->exportUxonObject();
// Remove any explicitly set widget id because we are going to instantiate
// a new widget, which will fail with the same explicit id
$uxon->unsetProperty('id');
} else {
$page = $task->getPageTriggeredOn();
$uxon = new UxonObject([
'widget_type' => 'Data',
'object_alias' => $this->getMetaObject()->getAliasWithNamespace()
]);
}
if ($uxonForCols !== null) {
$uxon->setProperty('columns', $uxonForCols);
}
if ($uxonForSorters !== null) {
$uxon->setProperty('sorters', $uxonForSorters);
}
return WidgetFactory::createFromUxon($page, $uxon);
}
}