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