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