aac1ff32d9afd6e3142e7dffd73d94e10e2d03eb
[GitHub/WoltLab/WCF.git] /
1 <?php
2 namespace wcf\system\package\plugin;
3 use wcf\data\acp\template\ACPTemplate;
4 use wcf\data\acp\template\ACPTemplateList;
5 use wcf\data\template\listener\TemplateListenerEditor;
6 use wcf\data\template\listener\TemplateListenerList;
7 use wcf\data\template\Template;
8 use wcf\data\template\TemplateList;
9 use wcf\system\cache\builder\TemplateListenerCodeCacheBuilder;
10 use wcf\system\devtools\pip\IDevtoolsPipEntryList;
11 use wcf\system\devtools\pip\IGuiPackageInstallationPlugin;
12 use wcf\system\devtools\pip\TXmlGuiPackageInstallationPlugin;
13 use wcf\system\form\builder\container\FormContainer;
14 use wcf\system\form\builder\field\dependency\ValueFormFieldDependency;
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\MultilineTextFormField;
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\WCF;
22 use wcf\util\StringUtil;
23
24 /**
25 * Installs, updates and deletes template listeners.
26 *
27 * @author Alexander Ebert, Matthias Schmidt
28 * @copyright 2001-2018 WoltLab GmbH
29 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
30 * @package WoltLabSuite\Core\System\Package\Plugin
31 */
32 class TemplateListenerPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IGuiPackageInstallationPlugin {
33 use TXmlGuiPackageInstallationPlugin {
34 setEntryData as defaultSetEntryData;
35 }
36
37 /**
38 * @inheritDoc
39 */
40 public $className = TemplateListenerEditor::class;
41
42 /**
43 * @inheritDoc
44 */
45 protected function handleDelete(array $items) {
46 $sql = "DELETE FROM wcf".WCF_N."_".$this->tableName."
47 WHERE packageID = ?
48 AND environment = ?
49 AND eventName = ?
50 AND name = ?
51 AND templateName = ?";
52 $statement = WCF::getDB()->prepareStatement($sql);
53 foreach ($items as $item) {
54 $statement->execute([
55 $this->installation->getPackageID(),
56 $item['elements']['environment'],
57 $item['elements']['eventname'],
58 $item['attributes']['name'],
59 $item['elements']['templatename']
60 ]);
61 }
62 }
63
64 /**
65 * @inheritDoc
66 */
67 protected function prepareImport(array $data) {
68 $niceValue = isset($data['elements']['nice']) ? intval($data['elements']['nice']) : 0;
69 if ($niceValue < -128) {
70 $niceValue = -128;
71 }
72 else if ($niceValue > 127) {
73 $niceValue = 127;
74 }
75
76 return [
77 'environment' => $data['elements']['environment'],
78 'eventName' => $data['elements']['eventname'],
79 'niceValue' => $niceValue,
80 'name' => $data['attributes']['name'],
81 'options' => isset($data['elements']['options']) ? StringUtil::normalizeCsv($data['elements']['options']) : '',
82 'permissions' => isset($data['elements']['permissions']) ? StringUtil::normalizeCsv($data['elements']['permissions']) : '',
83 'templateCode' => $data['elements']['templatecode'],
84 'templateName' => $data['elements']['templatename']
85 ];
86 }
87
88 /**
89 * @inheritDoc
90 */
91 protected function findExistingItem(array $data) {
92 $sql = "SELECT *
93 FROM wcf".WCF_N."_".$this->tableName."
94 WHERE packageID = ?
95 AND name = ?
96 AND templateName = ?
97 AND eventName = ?
98 AND environment = ?";
99 $parameters = [
100 $this->installation->getPackageID(),
101 $data['name'],
102 $data['templateName'],
103 $data['eventName'],
104 $data['environment']
105 ];
106
107 return [
108 'sql' => $sql,
109 'parameters' => $parameters
110 ];
111 }
112
113 /**
114 * @inheritDoc
115 */
116 protected function cleanup() {
117 // clear cache immediately
118 TemplateListenerCodeCacheBuilder::getInstance()->reset();
119 }
120
121 /**
122 * @inheritDoc
123 * @since 3.1
124 */
125 public static function getSyncDependencies() {
126 return [];
127 }
128
129 /**
130 * @inheritDoc
131 * @since 3.2
132 */
133 public function addFormFields(IFormDocument $form) {
134 $ldq = preg_quote(WCF::getTPL()->getCompiler()->getLeftDelimiter(), '~');
135 $rdq = preg_quote(WCF::getTPL()->getCompiler()->getRightDelimiter(), '~');
136
137 $getEvents = function($templateList) use ($ldq, $rdq) {
138 $templateEvents = [];
139 /** @var ACPTemplate|Template $template */
140 foreach ($templateList as $template) {
141 if (preg_match_all("~{$ldq}event\ name\=\'(?<event>[\w]+)\'{$rdq}~", $template->getSource(), $matches)) {
142 $templates[$template->templateName] = $template->templateName;
143
144 foreach ($matches['event'] as $event) {
145 if (!isset($templateEvents[$template->templateName])) {
146 $templateEvents[$template->templateName] = [];
147 }
148
149 $templateEvents[$template->templateName][] = $event;
150 }
151 }
152 }
153
154 foreach ($templateEvents as &$events) {
155 sort($events);
156 }
157 unset($events);
158
159 return $templateEvents;
160 };
161
162 $templateList = new TemplateList();
163 $templateList->getConditionBuilder()->add(
164 'template.packageID IN (?)',
165 [array_merge(
166 [$this->installation->getPackage()->packageID],
167 array_keys($this->installation->getPackage()->getAllRequiredPackages())
168 )]
169 );
170 $templateList->getConditionBuilder()->add('template.templateGroupID IS NULL');
171 $templateList->sqlOrderBy = 'template.templateName ASC';
172 $templateList->readObjects();
173
174 $templateEvents = $getEvents($templateList);
175
176 $acpTemplateList = new ACPTemplateList();
177 $acpTemplateList->getConditionBuilder()->add(
178 'acp_template.packageID IN (?)',
179 [array_merge(
180 [$this->installation->getPackage()->packageID],
181 array_keys($this->installation->getPackage()->getAllRequiredPackages())
182 )]
183 );
184 $acpTemplateList->sqlOrderBy = 'acp_template.templateName ASC';
185 $acpTemplateList->readObjects();
186
187 $acpTemplateEvents = $getEvents($acpTemplateList);
188
189 /** @var FormContainer $dataContainer */
190 $dataContainer = $form->getNodeById('data');
191
192 $dataContainer->appendChildren([
193 TextFormField::create('name')
194 ->label('wcf.acp.pip.templateListener.name')
195 ->description('wcf.acp.pip.templateListener.name.description')
196 ->required()
197 ->addValidator(new FormFieldValidator('format', function(TextFormField $formField) {
198 if (!preg_match('~^[a-z][A-z]+$~', $formField->getValue())) {
199 $formField->addValidationError(
200 new FormFieldValidationError(
201 'format',
202 'wcf.acp.pip.templateListener.name.error.format'
203 )
204 );
205 }
206 })),
207
208 SingleSelectionFormField::create('frontendTemplateName')
209 ->objectProperty('templatename')
210 ->label('wcf.acp.pip.templateListener.templateName')
211 ->description('wcf.acp.pip.templateListener.templateName.description')
212 ->required()
213 ->options(array_combine(array_keys($templateEvents), array_keys($templateEvents)))
214 ->filterable(),
215
216 SingleSelectionFormField::create('acpTemplateName')
217 ->objectProperty('templatename')
218 ->label('wcf.acp.pip.templateListener.templateName')
219 ->description('wcf.acp.pip.templateListener.templateName.description')
220 ->required()
221 ->options(array_combine(array_keys($acpTemplateEvents), array_keys($acpTemplateEvents)))
222 ->filterable()
223 ]);
224
225 foreach ($templateEvents as $templateName => $events) {
226 $dataContainer->appendChild(
227 SingleSelectionFormField::create($templateName . '_eventName')
228 ->objectProperty('eventName')
229 ->label('wcf.acp.pip.templateListener.eventName')
230 ->description('wcf.acp.pip.templateListener.eventName.description')
231 ->required()
232 ->options(array_combine($events, $events))
233 ->addDependency(
234 ValueFormFieldDependency::create('templateName')
235 ->field($form->getNodeById('frontendTemplateName'))
236 ->values([$templateName])
237 )
238 );
239 }
240
241 foreach ($acpTemplateEvents as $templateName => $events) {
242 $dataContainer->appendChild(
243 SingleSelectionFormField::create('acp_' . $templateName . '_eventName')
244 ->objectProperty('eventName')
245 ->label('wcf.acp.pip.templateListener.eventName')
246 ->description('wcf.acp.pip.templateListener.eventName.description')
247 ->required()
248 ->options(array_combine($events, $events))
249 ->addDependency(
250 ValueFormFieldDependency::create('acpTemplateName')
251 ->field($form->getNodeById('acpTemplateName'))
252 ->values([$templateName])
253 )
254 );
255 }
256
257 $dataContainer->appendChildren([
258 SingleSelectionFormField::create('environment')
259 ->label('wcf.acp.pip.templateListener.environment')
260 ->description('wcf.acp.pip.templateListener.environment.description')
261 ->required()
262 ->options([
263 'admin' => 'admin',
264 'user' => 'user'
265 ])
266 ->value('user')
267 ->addValidator(new FormFieldValidator('uniqueness', function(SingleSelectionFormField $formField) {
268 /** @var TextFormField $nameField */
269 $nameField = $formField->getDocument()->getNodeById('name');
270
271 /** @var SingleSelectionFormField $actionNameFormField */
272 $templateNameFormField = $formField->getDocument()->getNodeById('templateName');
273
274 /** @var SingleSelectionFormField $actionNameFormField */
275 $acpTemplateNameFormField = $formField->getDocument()->getNodeById('acpTemplateName');
276
277 if (
278 $formField->getDocument()->getFormMode() === IFormDocument::FORM_MODE_CREATE ||
279 $this->editedEntry->getAttribute('name') !== $nameField->getSaveValue() ||
280 $this->editedEntry->getElementsByTagName('environment')->item(0)->nodeValue !== $formField->getSaveValue() ||
281 (
282 $formField->getSaveValue() === 'admin' &&
283 $this->editedEntry->getElementsByTagName('templatename')->item(0)->nodeValue !== $acpTemplateNameFormField->getSaveValue()
284 ) ||
285 (
286 $formField->getSaveValue() === 'user' &&
287 $this->editedEntry->getElementsByTagName('templatename')->item(0)->nodeValue !== $templateNameFormField->getSaveValue()
288 )
289 ) {
290 $listenerList = new TemplateListenerList();
291 $listenerList->getConditionBuilder()->add(
292 'name = ?',
293 [$nameField->getSaveValue()]
294 );
295
296 if ($formField->getSaveValue() === 'admin') {
297 /** @var SingleSelectionFormField $templateNameField */
298 $templateNameField = $formField->getDocument()->getNodeById('acpTemplateName');
299
300 /** @var SingleSelectionFormField $eventNameField */
301 $eventNameField = $formField->getDocument()->getNodeById('acp_' . $templateNameField->getSaveValue() . '_eventName');
302 } else {
303 /** @var SingleSelectionFormField $templateNameField */
304 $templateNameField = $formField->getDocument()->getNodeById('frontendTemplateName');
305
306 /** @var SingleSelectionFormField $eventNameField */
307 $eventNameField = $formField->getDocument()->getNodeById($templateNameField->getSaveValue() . '_eventName');
308 }
309
310 $templateName = $templateNameField->getSaveValue();
311 $eventName = $eventNameField->getSaveValue();
312
313 $listenerList->getConditionBuilder()->add('templateName = ?', [$templateName]);
314 $listenerList->getConditionBuilder()->add('eventName = ?', [$eventName]);
315 $listenerList->getConditionBuilder()->add('environment = ?', [$formField->getSaveValue()]);
316
317 if ($listenerList->countObjects() > 0) {
318 $nameField->addValidationError(
319 new FormFieldValidationError(
320 'notUnique',
321 'wcf.acp.pip.templateListener.name.error.notUnique'
322 )
323 );
324 }
325 }
326 })),
327
328 // TODO: use field with code support
329 MultilineTextFormField::create('templateCode')
330 ->objectProperty('templatecode')
331 ->label('wcf.acp.pip.templateListener.templateCode')
332 ->description('wcf.acp.pip.templateListener.templateCode.description')
333 ->required()
334 ]);
335
336 $form->getNodeById('frontendTemplateName')->addDependency(
337 ValueFormFieldDependency::create('environment')
338 ->field($form->getNodeById('environment'))
339 ->values(['user'])
340 );
341 $form->getNodeById('acpTemplateName')->addDependency(
342 ValueFormFieldDependency::create('environment')
343 ->field($form->getNodeById('environment'))
344 ->values(['admin'])
345 );
346 }
347
348 /**
349 * @inheritDoc
350 * @since 3.2
351 */
352 protected function getElementData(\DOMElement $element, $saveData = false) {
353 return [
354 'environment' => $element->getElementsByTagName('environment')->item(0)->nodeValue,
355 'eventName' => $element->getElementsByTagName('eventname')->item(0)->nodeValue,
356 'name' => $element->getAttribute('name'),
357 'packageID' => $this->installation->getPackage()->packageID,
358 'templateCode' => $element->getElementsByTagName('templatecode')->item(0)->nodeValue,
359 'templateName' => $element->getElementsByTagName('templatename')->item(0)->nodeValue
360 ];
361 }
362
363 /**
364 * @inheritDoc
365 * @since 3.2
366 */
367 public function getElementIdentifier(\DOMElement $element) {
368 return sha1(
369 $element->getElementsByTagName('templatename')->item(0)->nodeValue . '/' .
370 $element->getElementsByTagName('eventname')->item(0)->nodeValue . '/' .
371 $element->getElementsByTagName('environment')->item(0)->nodeValue . '/' .
372 $element->getAttribute('name')
373 );
374 }
375
376 /**
377 * @inheritDoc
378 * @since 3.2
379 */
380 public function setEntryData($identifier, IFormDocument $document) {
381 if ($this->defaultSetEntryData($identifier, $document)) {
382 $data = $this->getElementData($this->getElementByIdentifier($this->getProjectXml(), $identifier));
383
384 switch ($data['environment']) {
385 case 'admin':
386 /** @var SingleSelectionFormField $templateName */
387 $templateName = $document->getNodeById('acpTemplateName');
388
389 /** @var SingleSelectionFormField $eventName */
390 $eventName = $document->getNodeById('acp_' . $data['templateName'] . '_eventName');
391 break;
392
393 case 'user':
394 /** @var SingleSelectionFormField $templateName */
395 $templateName = $document->getNodeById('templateName');
396
397 /** @var SingleSelectionFormField $eventName */
398 $eventName = $document->getNodeById($data['templateName'] . '_eventName');
399 break;
400
401 default:
402 throw new \LogicException("Unknown enviornment '{$data['environment']}'.");
403 }
404
405 $templateName->value($data['templateName']);
406 $eventName->value($data['eventName']);
407
408 return true;
409 }
410
411 return false;
412 }
413
414 /**
415 * @inheritDoc
416 * @since 3.2
417 */
418 protected function setEntryListKeys(IDevtoolsPipEntryList $entryList) {
419 $entryList->setKeys([
420 'name' => 'wcf.acp.pip.templateListener.name',
421 'templateName' => 'wcf.acp.pip.templateListener.templateName',
422 'eventName' => 'wcf.acp.pip.templateListener.eventName',
423 'environment' => 'wcf.acp.pip.templateListener.environment'
424 ]);
425 }
426
427 /**
428 * @inheritDoc
429 * @since 3.2
430 */
431 protected function createXmlElement(\DOMDocument $document, IFormDocument $form) {
432 $data = $form->getData()['data'];
433
434 $listener = $document->createElement($this->tagName);
435 $listener->setAttribute('name', $data['name']);
436
437 $listener->appendChild($document->createElement('environment', $data['environment']));
438 $listener->appendChild($document->createElement('templatename', $data['templatename']));
439 $listener->appendChild($document->createElement('eventname', $data['eventName']));
440
441 $templateCode = $document->createElement('templatecode');
442 $templateCode->appendChild($document->createCDATASection(StringUtil::unifyNewlines(StringUtil::escapeCDATA($data['templatecode']))));
443 $listener->appendChild($templateCode);
444
445 return $listener;
446 }
447 }