Merge branch '6.0'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / category / CategoryAction.class.php
CommitLineData
13d8b49b 1<?php
a9229942 2
13d8b49b 3namespace wcf\data\category;
a9229942 4
13d8b49b 5use wcf\data\AbstractDatabaseObjectAction;
cea5b918 6use wcf\data\ISortableAction;
a427a8c8 7use wcf\data\IToggleAction;
a6418d60 8use wcf\data\IToggleContainerAction;
a9229942 9use wcf\data\language\item\LanguageItemAction;
03ba10fa 10use wcf\data\TDatabaseObjectToggle;
7700d05b 11use wcf\system\acl\ACLHandler;
13d8b49b 12use wcf\system\category\CategoryHandler;
9c3f3eb2 13use wcf\system\database\util\PreparedStatementConditionBuilder;
13d8b49b 14use wcf\system\exception\PermissionDeniedException;
fc3d134b 15use wcf\system\exception\SystemException;
3631f7bd 16use wcf\system\exception\UserInputException;
13d8b49b
MS
17use wcf\system\user\collapsible\content\UserCollapsibleContentHandler;
18use wcf\system\WCF;
19
20/**
21 * Executes category-related actions.
a9229942
TD
22 *
23 * @author Matthias Schmidt
24 * @copyright 2001-2019 WoltLab GmbH
25 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
a9229942
TD
26 *
27 * @method Category create()
28 * @method CategoryEditor[] getObjects()
29 * @method CategoryEditor getSingleObject()
13d8b49b 30 */
a9229942
TD
31class CategoryAction extends AbstractDatabaseObjectAction implements
32 ISortableAction,
33 IToggleAction,
34 IToggleContainerAction
35{
36 use TDatabaseObjectToggle;
37
38 /**
39 * categorized object type
40 * @var \wcf\data\object\type\ObjectType
41 */
42 protected $objectType;
43
44 /**
45 * @inheritDoc
46 */
47 protected $requireACP = ['create', 'delete', 'toggle', 'update', 'updatePosition'];
48
49 /**
50 * @inheritDoc
51 */
52 public function delete()
53 {
54 // call category types
55 foreach ($this->getObjects() as $categoryEditor) {
56 $categoryEditor->getProcessor()->beforeDeletion($categoryEditor);
57 }
58
59 $returnValue = parent::delete();
60
7700d05b
MW
61 // delete acl
62 foreach ($this->getObjects() as $categoryEditor) {
63 $aclObjectTypeName = $categoryEditor->getObjectType()->getProcessor()->getObjectTypeName('com.woltlab.wcf.acl');
64 if ($aclObjectTypeName) {
65 ACLHandler::getInstance()->removeValues(
66 ACLHandler::getInstance()->getObjectTypeID($aclObjectTypeName),
67 [$categoryEditor->categoryID]
68 );
69 }
70 }
71
a9229942
TD
72 // delete language items
73 if (!empty($this->objects)) {
74 // identify i18n labels
75 $languageVariables = [];
76 foreach ($this->getObjects() as $category) {
77 if ($category->title === $category->getProcessor()->getI18nLangVarPrefix() . '.title.category' . $category->categoryID) {
78 $languageVariables[] = $category->title;
79 }
80 if ($category->description === $category->getProcessor()->getI18nLangVarPrefix() . '.description.category' . $category->categoryID) {
81 $languageVariables[] = $category->description;
82 }
83 }
84
85 // remove language variables
86 if (!empty($languageVariables)) {
87 $conditions = new PreparedStatementConditionBuilder();
88 $conditions->add('languageItem IN (?)', [$languageVariables]);
89
90 $sql = "SELECT languageItemID
91 FROM wcf" . WCF_N . "_language_item
92 " . $conditions;
93 $statement = WCF::getDB()->prepareStatement($sql);
94 $statement->execute($conditions->getParameters());
95 $languageItemIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
96
97 $objectAction = new LanguageItemAction($languageItemIDs, 'delete');
98 $objectAction->executeAction();
99 }
100 }
101
102 // call category types
103 foreach ($this->getObjects() as $categoryEditor) {
104 $categoryEditor->getProcessor()->afterDeletion($categoryEditor);
105 }
106
107 return $returnValue;
108 }
109
110 /**
111 * @inheritDoc
112 */
113 public function toggleContainer()
114 {
115 $collapsibleObjectTypeName = $this->getObjects()[0]->getProcessor()->getObjectTypeName('com.woltlab.wcf.collapsibleContent');
116 if ($collapsibleObjectTypeName === null) {
117 throw new SystemException("Categories of this type don't support collapsing");
118 }
119
120 $objectTypeID = UserCollapsibleContentHandler::getInstance()->getObjectTypeID($collapsibleObjectTypeName);
121 $collapsedCategories = UserCollapsibleContentHandler::getInstance()->getCollapsedContent($objectTypeID);
122
123 $categoryID = $this->objects[0]->categoryID;
124 if (\array_search($categoryID, $collapsedCategories) !== false) {
125 UserCollapsibleContentHandler::getInstance()->markAsOpened($objectTypeID, $categoryID);
126 } else {
127 UserCollapsibleContentHandler::getInstance()->markAsCollapsed($objectTypeID, $categoryID);
128 }
129 }
130
131 /**
132 * @inheritDoc
133 */
134 public function update()
135 {
136 // check if showOrder needs to be recalculated
137 if (\count($this->objects) == 1 && isset($this->parameters['data']['parentCategoryID']) && isset($this->parameters['data']['showOrder'])) {
138 $categoryEditor = $this->getObjects()[0];
139 if ($categoryEditor->parentCategoryID != $this->parameters['data']['parentCategoryID'] || $categoryEditor->showOrder != $this->parameters['data']['showOrder']) {
140 $this->parameters['data']['showOrder'] = $categoryEditor->updateShowOrder(
141 $this->parameters['data']['parentCategoryID'],
142 $this->parameters['data']['showOrder']
143 );
144 }
145 }
146
147 parent::update();
148
149 if (isset($this->parameters['data']['parentCategoryID'])) {
150 $objectType = null;
151 $parentUpdates = [];
152
153 foreach ($this->getObjects() as $category) {
154 if ($objectType === null) {
155 $objectType = $category->getObjectType();
156 }
157
158 if ($category->parentCategoryID != $this->parameters['data']['parentCategoryID']) {
159 $parentUpdates[$category->categoryID] = [
160 'oldParentCategoryID' => $category->parentCategoryID,
161 'newParentCategoryID' => $this->parameters['data']['parentCategoryID'],
162 ];
163 }
164 }
165
166 if (!empty($parentUpdates)) {
167 $objectType->getProcessor()->changedParentCategories($parentUpdates);
168 }
169 }
170 }
171
172 /**
173 * @inheritDoc
174 */
175 public function updatePosition()
176 {
177 $objectType = null;
178 $parentUpdates = [];
179
180 WCF::getDB()->beginTransaction();
181 foreach ($this->parameters['data']['structure'] as $parentCategoryID => $categoryIDs) {
182 $showOrder = 1;
183 foreach ($categoryIDs as $categoryID) {
184 $category = CategoryHandler::getInstance()->getCategory($categoryID);
185 if ($objectType === null) {
186 $objectType = $category->getObjectType();
187 }
188
189 if ($category->parentCategoryID != $parentCategoryID) {
190 $parentUpdates[$categoryID] = [
191 'oldParentCategoryID' => $category->parentCategoryID,
192 'newParentCategoryID' => $parentCategoryID,
193 ];
194 }
195
196 $this->objects[$categoryID]->update([
197 'parentCategoryID' => $parentCategoryID ? $this->objects[$parentCategoryID]->categoryID : 0,
198 'showOrder' => $showOrder++,
199 ]);
200 }
201 }
202 WCF::getDB()->commitTransaction();
203
204 if (!empty($parentUpdates)) {
205 $objectType->getProcessor()->changedParentCategories($parentUpdates);
206 }
207 }
208
209 /**
210 * @inheritDoc
211 */
212 public function validateCreate()
213 {
214 $this->readInteger('objectTypeID', false, 'data');
215
216 $objectType = CategoryHandler::getInstance()->getObjectType($this->parameters['data']['objectTypeID']);
217 if ($objectType === null) {
218 throw new UserInputException('objectTypeID', 'invalid');
219 }
220 if (!$objectType->getProcessor()->canAddCategory()) {
221 throw new PermissionDeniedException();
222 }
223 }
224
225 /**
226 * @inheritDoc
227 */
228 public function validateDelete()
229 {
230 // read objects
231 if (empty($this->objects)) {
232 $this->readObjects();
233
234 if (empty($this->objects)) {
235 throw new UserInputException('objectIDs');
236 }
237 }
238
239 foreach ($this->getObjects() as $categoryEditor) {
240 if (!$categoryEditor->getProcessor()->canDeleteCategory()) {
241 throw new PermissionDeniedException();
242 }
243 }
244 }
245
246 /**
247 * @inheritDoc
248 */
249 public function validateToggleContainer()
250 {
251 $this->validateUpdate();
252 }
253
254 /**
255 * @inheritDoc
256 */
257 public function validateUpdate()
258 {
259 // read objects
260 if (empty($this->objects)) {
261 $this->readObjects();
262
263 if (empty($this->objects)) {
264 throw new UserInputException('objectIDs');
265 }
266 }
267
268 foreach ($this->getObjects() as $categoryEditor) {
269 if (!$categoryEditor->getProcessor()->canEditCategory()) {
270 throw new PermissionDeniedException();
271 }
272 }
273 }
274
275 /**
276 * @inheritDoc
277 */
278 public function validateUpdatePosition()
279 {
280 // validate 'structure' parameter
281 if (!isset($this->parameters['data']['structure']) || !\is_array($this->parameters['data']['structure'])) {
282 throw new UserInputException('structure');
283 }
284
285 // validate given category ids
286 foreach ($this->parameters['data']['structure'] as $parentCategoryID => $categoryIDs) {
287 if ($parentCategoryID) {
288 // validate category
289 $category = CategoryHandler::getInstance()->getCategory($parentCategoryID);
290 if ($category === null) {
291 throw new UserInputException('structure');
292 }
293
294 // validate permissions
295 if (!$category->getProcessor()->canEditCategory()) {
296 throw new PermissionDeniedException();
297 }
298
299 $this->objects[$category->categoryID] = new $this->className($category);
300 }
301
302 foreach ($categoryIDs as $categoryID) {
303 // validate category
304 $category = CategoryHandler::getInstance()->getCategory($categoryID);
305 if ($category === null) {
306 throw new UserInputException('structure');
307 }
308
309 // validate permissions
310 if (!$category->getProcessor()->canEditCategory()) {
311 throw new PermissionDeniedException();
312 }
313
314 $this->objects[$category->categoryID] = new $this->className($category);
315 }
316 }
317 }
13d8b49b 318}