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