1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the TYPO3 CMS project. |
5
|
|
|
* |
6
|
|
|
* It is free software; you can redistribute it and/or modify it under |
7
|
|
|
* the terms of the GNU General Public License, either version 2 |
8
|
|
|
* of the License, or any later version. |
9
|
|
|
* |
10
|
|
|
* For the full copyright and license information, please read the |
11
|
|
|
* LICENSE.txt file that was distributed with this source code. |
12
|
|
|
* |
13
|
|
|
* The TYPO3 project - inspiring people to share! |
14
|
|
|
*/ |
15
|
|
|
|
16
|
|
|
namespace TYPO3\CMS\Workspaces\Service\Dependency; |
17
|
|
|
|
18
|
|
|
use TYPO3\CMS\Core\SingletonInterface; |
19
|
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility; |
20
|
|
|
use TYPO3\CMS\Workspaces\Dependency; |
21
|
|
|
use TYPO3\CMS\Workspaces\Dependency\DependencyResolver; |
22
|
|
|
use TYPO3\CMS\Workspaces\Dependency\ElementEntity; |
23
|
|
|
use TYPO3\CMS\Workspaces\Dependency\ElementEntityProcessor; |
24
|
|
|
use TYPO3\CMS\Workspaces\Dependency\EventCallback; |
25
|
|
|
use TYPO3\CMS\Workspaces\Dependency\ReferenceEntity; |
26
|
|
|
use TYPO3\CMS\Workspaces\Service\GridDataService; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Service to collect dependent elements. |
30
|
|
|
*/ |
31
|
|
|
class CollectionService implements SingletonInterface |
32
|
|
|
{ |
33
|
|
|
/** |
34
|
|
|
* @var \TYPO3\CMS\Core\DataHandling\DataHandler |
35
|
|
|
*/ |
36
|
|
|
protected $dataHandler; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var Dependency\ElementEntityProcessor |
40
|
|
|
*/ |
41
|
|
|
protected $elementEntityProcessor; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var Dependency\DependencyResolver |
45
|
|
|
*/ |
46
|
|
|
protected $dependencyResolver; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var array |
50
|
|
|
*/ |
51
|
|
|
protected $dataArray; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @var array |
55
|
|
|
*/ |
56
|
|
|
protected $nestedDataArray; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* @return Dependency\DependencyResolver |
60
|
|
|
*/ |
61
|
|
|
public function getDependencyResolver() |
62
|
|
|
{ |
63
|
|
|
if (!isset($this->dependencyResolver)) { |
64
|
|
|
$this->dependencyResolver = GeneralUtility::makeInstance(DependencyResolver::class); |
65
|
|
|
$this->dependencyResolver->setOuterMostParentsRequireReferences(true); |
66
|
|
|
$this->dependencyResolver->setWorkspace($this->getWorkspace()); |
67
|
|
|
|
68
|
|
|
$this->dependencyResolver->setEventCallback( |
69
|
|
|
ElementEntity::EVENT_Construct, |
70
|
|
|
$this->getDependencyCallback('createNewDependentElementCallback') |
71
|
|
|
); |
72
|
|
|
|
73
|
|
|
$this->dependencyResolver->setEventCallback( |
74
|
|
|
ElementEntity::EVENT_CreateChildReference, |
75
|
|
|
$this->getDependencyCallback('createNewDependentElementChildReferenceCallback') |
76
|
|
|
); |
77
|
|
|
|
78
|
|
|
$this->dependencyResolver->setEventCallback( |
79
|
|
|
ElementEntity::EVENT_CreateParentReference, |
80
|
|
|
$this->getDependencyCallback('createNewDependentElementParentReferenceCallback') |
81
|
|
|
); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
return $this->dependencyResolver; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Gets a new callback to be used in the dependency resolver utility. |
89
|
|
|
* |
90
|
|
|
* @param string $method |
91
|
|
|
* @param array $targetArguments |
92
|
|
|
* @return Dependency\EventCallback |
93
|
|
|
*/ |
94
|
|
|
protected function getDependencyCallback($method, array $targetArguments = []) |
95
|
|
|
{ |
96
|
|
|
return GeneralUtility::makeInstance( |
97
|
|
|
EventCallback::class, |
98
|
|
|
$this->getElementEntityProcessor(), |
99
|
|
|
$method, |
100
|
|
|
$targetArguments |
101
|
|
|
); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* Gets the element entity processor. |
106
|
|
|
* |
107
|
|
|
* @return Dependency\ElementEntityProcessor |
108
|
|
|
*/ |
109
|
|
|
protected function getElementEntityProcessor() |
110
|
|
|
{ |
111
|
|
|
if (!isset($this->elementEntityProcessor)) { |
112
|
|
|
$this->elementEntityProcessor = GeneralUtility::makeInstance(ElementEntityProcessor::class); |
113
|
|
|
$this->elementEntityProcessor->setWorkspace($this->getWorkspace()); |
114
|
|
|
} |
115
|
|
|
return $this->elementEntityProcessor; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Gets the current workspace id. |
120
|
|
|
* |
121
|
|
|
* @return int |
122
|
|
|
*/ |
123
|
|
|
protected function getWorkspace() |
124
|
|
|
{ |
125
|
|
|
return (int)$GLOBALS['BE_USER']->workspace; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Processes the data array |
130
|
|
|
* |
131
|
|
|
* @param array $dataArray |
132
|
|
|
* @return array |
133
|
|
|
*/ |
134
|
|
|
public function process(array $dataArray) |
135
|
|
|
{ |
136
|
|
|
$collection = 0; |
137
|
|
|
$this->dataArray = $dataArray; |
138
|
|
|
$this->nestedDataArray = []; |
139
|
|
|
|
140
|
|
|
$outerMostParents = $this->getDependencyResolver()->getOuterMostParents(); |
141
|
|
|
|
142
|
|
|
if (empty($outerMostParents)) { |
143
|
|
|
return $this->dataArray; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
// For each outer most parent, get all nested child elements: |
147
|
|
|
foreach ($outerMostParents as $outerMostParent) { |
148
|
|
|
$this->resolveDataArrayChildDependencies( |
149
|
|
|
$outerMostParent, |
150
|
|
|
++$collection |
151
|
|
|
); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
$processedDataArray = $this->finalize($this->dataArray); |
155
|
|
|
|
156
|
|
|
unset($this->dataArray); |
157
|
|
|
unset($this->nestedDataArray); |
158
|
|
|
|
159
|
|
|
return $processedDataArray; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Applies structures to instance data array and |
164
|
|
|
* ensures children are added below accordant parent |
165
|
|
|
* |
166
|
|
|
* @param array $dataArray |
167
|
|
|
* @return array |
168
|
|
|
*/ |
169
|
|
|
protected function finalize(array $dataArray) |
170
|
|
|
{ |
171
|
|
|
$processedDataArray = []; |
172
|
|
|
foreach ($dataArray as $dataElement) { |
173
|
|
|
$dataElementIdentifier = $dataElement['id']; |
174
|
|
|
$processedDataArray[] = $dataElement; |
175
|
|
|
// Insert children (if any) |
176
|
|
|
if (!empty($this->nestedDataArray[$dataElementIdentifier])) { |
177
|
|
|
$processedDataArray = array_merge( |
178
|
|
|
$processedDataArray, |
179
|
|
|
$this->finalize($this->nestedDataArray[$dataElementIdentifier]) |
180
|
|
|
); |
181
|
|
|
unset($this->nestedDataArray[$dataElementIdentifier]); |
182
|
|
|
} |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
return $processedDataArray; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Resolves nested child dependencies. |
190
|
|
|
* |
191
|
|
|
* @param Dependency\ElementEntity $parent |
192
|
|
|
* @param int $collection |
193
|
|
|
* @param string $nextParentIdentifier |
194
|
|
|
* @param int $collectionLevel |
195
|
|
|
*/ |
196
|
|
|
protected function resolveDataArrayChildDependencies(ElementEntity $parent, $collection, $nextParentIdentifier = '', $collectionLevel = 0) |
197
|
|
|
{ |
198
|
|
|
$parentIdentifier = $parent->__toString(); |
199
|
|
|
$parentIsSet = isset($this->dataArray[$parentIdentifier]); |
200
|
|
|
|
201
|
|
|
if ($parentIsSet) { |
202
|
|
|
$this->dataArray[$parentIdentifier][GridDataService::GridColumn_Collection] = $collection; |
203
|
|
|
$this->dataArray[$parentIdentifier][GridDataService::GridColumn_CollectionLevel] = $collectionLevel; |
204
|
|
|
$this->dataArray[$parentIdentifier][GridDataService::GridColumn_CollectionCurrent] = md5($parentIdentifier); |
205
|
|
|
$this->dataArray[$parentIdentifier][GridDataService::GridColumn_CollectionChildren] = $this->getCollectionChildrenCount($parent->getChildren()); |
206
|
|
|
$nextParentIdentifier = $parentIdentifier; |
207
|
|
|
$collectionLevel++; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
foreach ($parent->getChildren() as $child) { |
211
|
|
|
$this->resolveDataArrayChildDependencies( |
212
|
|
|
$child->getElement(), |
213
|
|
|
$collection, |
214
|
|
|
$nextParentIdentifier, |
215
|
|
|
$collectionLevel |
216
|
|
|
); |
217
|
|
|
|
218
|
|
|
$childIdentifier = $child->getElement()->__toString(); |
219
|
|
|
if (!empty($nextParentIdentifier) && isset($this->dataArray[$childIdentifier])) { |
220
|
|
|
// Remove from dataArray, but collect to process later |
221
|
|
|
// and add it just next to the accordant parent element |
222
|
|
|
$this->dataArray[$childIdentifier][GridDataService::GridColumn_CollectionParent] = md5($nextParentIdentifier); |
223
|
|
|
$this->nestedDataArray[$nextParentIdentifier][] = $this->dataArray[$childIdentifier]; |
224
|
|
|
unset($this->dataArray[$childIdentifier]); |
225
|
|
|
} |
226
|
|
|
} |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* Return count of children, present in the data array |
231
|
|
|
* |
232
|
|
|
* @param ReferenceEntity[] $children |
233
|
|
|
* @return int |
234
|
|
|
*/ |
235
|
|
|
protected function getCollectionChildrenCount(array $children): int |
236
|
|
|
{ |
237
|
|
|
return count( |
238
|
|
|
array_filter($children, function (ReferenceEntity $child) { |
239
|
|
|
return isset($this->dataArray[$child->getElement()->__toString()]); |
240
|
|
|
}) |
241
|
|
|
); |
242
|
|
|
} |
243
|
|
|
} |
244
|
|
|
|