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