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