9e295764646af5214d830594fde9e062487b598d
[GitHub/WoltLab/WCF.git] /
1 <?php
2
3 namespace wcf\system\package\plugin;
4
5 use wcf\data\object\type\ObjectTypeCache;
6 use wcf\data\user\notification\event\UserNotificationEvent;
7 use wcf\data\user\notification\event\UserNotificationEventEditor;
8 use wcf\data\user\notification\event\UserNotificationEventList;
9 use wcf\system\devtools\pip\IDevtoolsPipEntryList;
10 use wcf\system\devtools\pip\IGuiPackageInstallationPlugin;
11 use wcf\system\devtools\pip\TXmlGuiPackageInstallationPlugin;
12 use wcf\system\exception\SystemException;
13 use wcf\system\form\builder\container\FormContainer;
14 use wcf\system\form\builder\field\BooleanFormField;
15 use wcf\system\form\builder\field\ClassNameFormField;
16 use wcf\system\form\builder\field\option\OptionFormField;
17 use wcf\system\form\builder\field\SingleSelectionFormField;
18 use wcf\system\form\builder\field\TextFormField;
19 use wcf\system\form\builder\field\user\group\option\UserGroupOptionFormField;
20 use wcf\system\form\builder\field\validation\FormFieldValidationError;
21 use wcf\system\form\builder\field\validation\FormFieldValidator;
22 use wcf\system\form\builder\IFormDocument;
23 use wcf\system\user\notification\event\IUserNotificationEvent;
24 use wcf\system\WCF;
25 use wcf\util\StringUtil;
26
27 /**
28 * Installs, updates and deletes user notification events.
29 *
30 * @author Matthias Schmidt, Marcel Werk
31 * @copyright 2001-2019 WoltLab GmbH
32 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
33 * @package WoltLabSuite\Core\System\Package\Plugin
34 */
35 class UserNotificationEventPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements
36 IGuiPackageInstallationPlugin
37 {
38 use TXmlGuiPackageInstallationPlugin;
39
40 /**
41 * @inheritDoc
42 */
43 public $className = UserNotificationEventEditor::class;
44
45 /**
46 * @inheritDoc
47 */
48 public $tableName = 'user_notification_event';
49
50 /**
51 * @inheritDoc
52 */
53 public $tagName = 'event';
54
55 /**
56 * preset event ids
57 * @var int[]
58 */
59 protected $presetEventIDs = [];
60
61 /**
62 * @inheritDoc
63 */
64 protected function handleDelete(array $items)
65 {
66 $sql = "DELETE FROM wcf" . WCF_N . "_" . $this->tableName . "
67 WHERE packageID = ?
68 AND objectTypeID = ?
69 AND eventName = ?";
70 $statement = WCF::getDB()->prepareStatement($sql);
71 foreach ($items as $item) {
72 $statement->execute([
73 $this->installation->getPackageID(),
74 $this->getObjectTypeID($item['elements']['objecttype']),
75 $item['elements']['name'],
76 ]);
77 }
78 }
79
80 /**
81 * @inheritDoc
82 */
83 protected function prepareImport(array $data)
84 {
85 $presetMailNotificationType = 'none';
86 if (isset($data['elements']['presetmailnotificationtype']) && ($data['elements']['presetmailnotificationtype'] == 'instant' || $data['elements']['presetmailnotificationtype'] == 'daily')) {
87 $presetMailNotificationType = $data['elements']['presetmailnotificationtype'];
88 }
89
90 return [
91 'eventName' => $data['elements']['name'],
92 'className' => $data['elements']['classname'],
93 'objectTypeID' => $this->getObjectTypeID($data['elements']['objecttype']),
94 'permissions' => isset($data['elements']['permissions']) ? StringUtil::normalizeCsv($data['elements']['permissions']) : '',
95 'options' => isset($data['elements']['options']) ? StringUtil::normalizeCsv($data['elements']['options']) : '',
96 'preset' => !empty($data['elements']['preset']) ? 1 : 0,
97 'presetMailNotificationType' => $presetMailNotificationType,
98 ];
99 }
100
101 /**
102 * @inheritDoc
103 */
104 protected function import(array $row, array $data)
105 {
106 /** @var UserNotificationEvent $event */
107 $event = parent::import($row, $data);
108
109 if (empty($row) && $data['preset']) {
110 $this->presetEventIDs[$event->eventID] = $data['presetMailNotificationType'];
111 }
112
113 return $event;
114 }
115
116 /**
117 * @inheritDoc
118 */
119 protected function cleanup()
120 {
121 if (empty($this->presetEventIDs)) {
122 return;
123 }
124
125 $sql = "INSERT IGNORE INTO wcf" . WCF_N . "_user_notification_event_to_user
126 (userID, eventID, mailNotificationType)
127 SELECT userID, ?, ?
128 FROM wcf" . WCF_N . "_user";
129 $statement = WCF::getDB()->prepareStatement($sql);
130 WCF::getDB()->beginTransaction();
131 foreach ($this->presetEventIDs as $eventID => $mailNotificationType) {
132 $statement->execute([$eventID, $mailNotificationType]);
133 }
134 WCF::getDB()->commitTransaction();
135 }
136
137 /**
138 * @inheritDoc
139 */
140 protected function findExistingItem(array $data)
141 {
142 $sql = "SELECT *
143 FROM wcf" . WCF_N . "_" . $this->tableName . "
144 WHERE objectTypeID = ?
145 AND eventName = ?";
146 $parameters = [
147 $data['objectTypeID'],
148 $data['eventName'],
149 ];
150
151 return [
152 'sql' => $sql,
153 'parameters' => $parameters,
154 ];
155 }
156
157 /**
158 * Gets the id of given object type id.
159 *
160 * @param string $objectType
161 * @return int
162 * @throws SystemException
163 */
164 protected function getObjectTypeID($objectType)
165 {
166 // get object type id
167 $sql = "SELECT object_type.objectTypeID
168 FROM wcf" . WCF_N . "_object_type object_type
169 WHERE object_type.objectType = ?
170 AND object_type.definitionID IN (
171 SELECT definitionID
172 FROM wcf" . WCF_N . "_object_type_definition
173 WHERE definitionName = 'com.woltlab.wcf.notification.objectType'
174 )";
175 $statement = WCF::getDB()->prepareStatement($sql, 1);
176 $statement->execute([$objectType]);
177 $row = $statement->fetchArray();
178 if (empty($row['objectTypeID'])) {
179 throw new SystemException("unknown notification object type '" . $objectType . "' given");
180 }
181
182 return $row['objectTypeID'];
183 }
184
185 /**
186 * @inheritDoc
187 * @since 3.1
188 */
189 public static function getSyncDependencies()
190 {
191 return ['objectType'];
192 }
193
194 /**
195 * @inheritDoc
196 * @since 5.2
197 */
198 protected function addFormFields(IFormDocument $form)
199 {
200 /** @var FormContainer $dataContainer */
201 $dataContainer = $form->getNodeById('data');
202
203 $dataContainer->appendChildren([
204 TextFormField::create('eventName')
205 ->objectProperty('name')
206 ->label('wcf.acp.pip.userNotificationEvent.eventName')
207 ->description('wcf.acp.pip.userNotificationEvent.eventName.description')
208 ->required()
209 ->addValidator(new FormFieldValidator('format', static function (TextFormField $formField) {
210 if (!\preg_match('~^[a-z][A-z]+$~', $formField->getValue())) {
211 $formField->addValidationError(
212 new FormFieldValidationError(
213 'format',
214 'wcf.acp.pip.userNotificationEvent.eventName.error.format'
215 )
216 );
217 }
218 })),
219
220 SingleSelectionFormField::create('objectType')
221 ->objectProperty('objecttype')
222 ->label('wcf.acp.pip.userNotificationEvent.objectType')
223 ->description('wcf.acp.pip.userNotificationEvent.objectType.description')
224 ->required()
225 ->options(static function () {
226 $options = [];
227 foreach (ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.notification.objectType') as $objectType) {
228 $options[$objectType->objectType] = $objectType->objectType;
229 }
230
231 \asort($options);
232
233 return $options;
234 })
235 // validate the uniqueness of the `name` field after knowing that the selected object type is valid
236 ->addValidator(new FormFieldValidator('nameUniqueness', function (SingleSelectionFormField $formField) {
237 /** @var TextFormField $nameField */
238 $nameField = $formField->getDocument()->getNodeById('eventName');
239
240 if (
241 $formField->getDocument()->getFormMode() === IFormDocument::FORM_MODE_CREATE
242 || $this->editedEntry->getElementsByTagName('name')->item(0)->nodeValue !== $nameField->getSaveValue()
243 || $this->editedEntry->getElementsByTagName('objecttype')->item(0)->nodeValue !== $formField->getSaveValue()
244 ) {
245 $eventList = new UserNotificationEventList();
246 $eventList->getConditionBuilder()->add(
247 'user_notification_event.eventName = ?',
248 [$nameField->getSaveValue()]
249 );
250 $eventList->getConditionBuilder()->add('user_notification_event.objectTypeID = ?', [
251 ObjectTypeCache::getInstance()->getObjectTypeIDByName(
252 'com.woltlab.wcf.notification.objectType',
253 $formField->getSaveValue()
254 ),
255 ]);
256 $eventList->getConditionBuilder()->add(
257 'user_notification_event.objectTypeID = ?',
258 [
259 ObjectTypeCache::getInstance()->getObjectTypeByName(
260 'com.woltlab.wcf.notification.objectType',
261 $formField->getSaveValue()
262 )->objectTypeID,
263 ]
264 );
265
266 if ($eventList->countObjects() > 0) {
267 $nameField->addValidationError(
268 new FormFieldValidationError(
269 'notUnique',
270 'wcf.acp.pip.userNotificationEvent.eventName.error.notUnique'
271 )
272 );
273 }
274 }
275 })),
276
277 ClassNameFormField::create()
278 ->objectProperty('classname')
279 ->required()
280 ->implementedInterface(IUserNotificationEvent::class),
281
282 BooleanFormField::create('preset')
283 ->label('wcf.acp.pip.userNotificationEvent.preset')
284 ->description('wcf.acp.pip.userNotificationEvent.preset.description'),
285
286 OptionFormField::create()
287 ->description('wcf.acp.pip.userNotificationEvent.options.description')
288 ->packageIDs(\array_merge(
289 [$this->installation->getPackage()->packageID],
290 \array_keys($this->installation->getPackage()->getAllRequiredPackages())
291 )),
292
293 UserGroupOptionFormField::create()
294 ->description('wcf.acp.pip.userNotificationEvent.permissions.description')
295 ->packageIDs(\array_merge(
296 [$this->installation->getPackage()->packageID],
297 \array_keys($this->installation->getPackage()->getAllRequiredPackages())
298 )),
299
300 SingleSelectionFormField::create('presetMailNotificationType')
301 ->objectProperty('presetmailnotificationtype')
302 ->label('wcf.acp.pip.userNotificationEvent.presetMailNotificationType')
303 ->description('wcf.acp.pip.userNotificationEvent.presetMailNotificationType.description')
304 ->nullable()
305 ->options([
306 '' => 'wcf.user.notification.mailNotificationType.none',
307 'daily' => 'wcf.user.notification.mailNotificationType.daily',
308 'instant' => 'wcf.user.notification.mailNotificationType.instant',
309 ]),
310 ]);
311 }
312
313 /**
314 * @inheritDoc
315 * @since 5.2
316 */
317 protected function fetchElementData(\DOMElement $element, $saveData)
318 {
319 $data = [
320 'className' => $element->getElementsByTagName('classname')->item(0)->nodeValue,
321 'eventName' => $element->getElementsByTagName('name')->item(0)->nodeValue,
322 'packageID' => $this->installation->getPackage()->packageID,
323 'preset' => 0,
324 ];
325
326 $objectType = $element->getElementsByTagName('objecttype')->item(0)->nodeValue;
327 if ($saveData) {
328 $data['objectTypeID'] = $this->getObjectTypeID($objectType);
329 } else {
330 $data['objectType'] = $objectType;
331 }
332
333 $options = $element->getElementsByTagName('options')->item(0);
334 if ($options) {
335 $data['options'] = StringUtil::normalizeCsv($options->nodeValue);
336 } elseif ($saveData) {
337 $data['options'] = '';
338 }
339
340 $permissions = $element->getElementsByTagName('permissions')->item(0);
341 if ($permissions) {
342 $data['permissions'] = StringUtil::normalizeCsv($permissions->nodeValue);
343 } elseif ($saveData) {
344 $data['permissions'] = '';
345 }
346
347 // the presence of a `preset` element is treated as `<preset>1</preset>
348 if ($element->getElementsByTagName('preset')->length === 1) {
349 $data['preset'] = 1;
350 } elseif ($saveData) {
351 $data['preset'] = 0;
352 }
353
354 $presetMailNotificationType = $element->getElementsByTagName('presetmailnotificationtype')->item(0);
355 if ($presetMailNotificationType && \in_array($presetMailNotificationType->nodeValue, ['instant', 'daily'])) {
356 $data['presetMailNotificationType'] = $presetMailNotificationType->nodeValue;
357 } elseif ($saveData) {
358 $data['presetMailNotificationType'] = 'none';
359 }
360
361 return $data;
362 }
363
364 /**
365 * @inheritDoc
366 * @since 5.2
367 */
368 public function getElementIdentifier(\DOMElement $element)
369 {
370 return \sha1(
371 $element->getElementsByTagName('name')->item(0)->nodeValue . '/'
372 . $element->getElementsByTagName('objecttype')->item(0)->nodeValue
373 );
374 }
375
376 /**
377 * @inheritDoc
378 * @since 5.2
379 */
380 protected function setEntryListKeys(IDevtoolsPipEntryList $entryList)
381 {
382 $entryList->setKeys([
383 'eventName' => 'wcf.acp.pip.userNotificationEvent.eventName',
384 'className' => 'wcf.form.field.className',
385 ]);
386 }
387
388 /**
389 * @inheritDoc
390 * @since 5.2
391 */
392 protected function prepareXmlElement(\DOMDocument $document, IFormDocument $form)
393 {
394 $event = $document->createElement($this->tagName);
395
396 $this->appendElementChildren(
397 $event,
398 [
399 'name',
400 'objecttype',
401 'classname',
402 'options' => '',
403 'permissions' => '',
404 'preset' => 0,
405 'presetmailnotificationtype' => '',
406 ],
407 $form
408 );
409
410 return $event;
411 }
412
413 /**
414 * @inheritDoc
415 * @since 5.2
416 */
417 protected function prepareDeleteXmlElement(\DOMElement $element)
418 {
419 $userNotificationEvent = $element->ownerDocument->createElement($this->tagName);
420
421 foreach (['name', 'objecttype'] as $childElement) {
422 $userNotificationEvent->appendChild($element->ownerDocument->createElement(
423 $childElement,
424 $element->getElementsByTagName($childElement)->item(0)->nodeValue
425 ));
426 }
427
428 return $userNotificationEvent;
429 }
430
431 /**
432 * @inheritDoc
433 * @since 5.2
434 */
435 protected function deleteObject(\DOMElement $element)
436 {
437 $elements = [];
438 foreach (['name', 'objecttype'] as $childElement) {
439 $elements[$childElement] = $element->getElementsByTagName($childElement)->item(0)->nodeValue;
440 }
441
442 $this->handleDelete([['elements' => $elements]]);
443 }
444 }