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\data\processor\CustomFormFieldDataProcessor;
15 use wcf\system\form\builder\field\dependency\ValueFormFieldDependency;
16 use wcf\system\form\builder\field\IntegerFormField;
17 use wcf\system\form\builder\field\option\OptionFormField;
18 use wcf\system\form\builder\field\user\group\option\UserGroupOptionFormField;
19 use wcf\system\form\builder\field\validation\FormFieldValidationError;
20 use wcf\system\form\builder\field\validation\FormFieldValidator;
21 use wcf\system\form\builder\field\MultilineTextFormField;
22 use wcf\system\form\builder\field\SingleSelectionFormField;
23 use wcf\system\form\builder\field\TextFormField;
24 use wcf\system\form\builder\IFormDocument;
26 use wcf\util\StringUtil;
29 * Installs, updates and deletes template listeners.
31 * @author Alexander Ebert, Matthias Schmidt
32 * @copyright 2001-2018 WoltLab GmbH
33 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
34 * @package WoltLabSuite\Core\System\Package\Plugin
36 class TemplateListenerPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IGuiPackageInstallationPlugin {
37 use TXmlGuiPackageInstallationPlugin {
38 setEntryData as defaultSetEntryData;
44 public $className = TemplateListenerEditor::class;
49 protected function handleDelete(array $items) {
50 $sql = "DELETE FROM wcf".WCF_N."_".$this->tableName."
55 AND templateName = ?";
56 $statement = WCF::getDB()->prepareStatement($sql);
57 foreach ($items as $item) {
59 $this->installation->getPackageID(),
60 $item['elements']['environment'],
61 $item['elements']['eventname'],
62 $item['attributes']['name'],
63 $item['elements']['templatename']
71 protected function prepareImport(array $data) {
72 $niceValue = isset($data['elements']['nice']) ? intval($data['elements']['nice']) : 0;
73 if ($niceValue < -128) {
76 else if ($niceValue > 127) {
81 'environment' => $data['elements']['environment'],
82 'eventName' => $data['elements']['eventname'],
83 'niceValue' => $niceValue,
84 'name' => $data['attributes']['name'],
85 'options' => isset($data['elements']['options']) ? StringUtil::normalizeCsv($data['elements']['options']) : '',
86 'permissions' => isset($data['elements']['permissions']) ? StringUtil::normalizeCsv($data['elements']['permissions']) : '',
87 'templateCode' => $data['elements']['templatecode'],
88 'templateName' => $data['elements']['templatename']
95 protected function findExistingItem(array $data) {
97 FROM wcf".WCF_N."_".$this->tableName."
102 AND environment = ?";
104 $this->installation->getPackageID(),
106 $data['templateName'],
113 'parameters' => $parameters
120 protected function cleanup() {
121 // clear cache immediately
122 TemplateListenerCodeCacheBuilder::getInstance()->reset();
129 public static function getSyncDependencies() {
137 protected function addFormFields(IFormDocument $form) {
138 $ldq = preg_quote(WCF::getTPL()->getCompiler()->getLeftDelimiter(), '~');
139 $rdq = preg_quote(WCF::getTPL()->getCompiler()->getRightDelimiter(), '~');
141 $getEvents = function($templateList) use ($ldq, $rdq) {
142 $templateEvents = [];
143 /** @var ACPTemplate|Template $template */
144 foreach ($templateList as $template) {
145 if (preg_match_all("~{$ldq}event\ name\=\'(?<event>[\w]+)\'{$rdq}~", $template->getSource(), $matches)) {
146 $templates[$template->templateName] = $template->templateName;
148 foreach ($matches['event'] as $event) {
149 if (!isset($templateEvents[$template->templateName])) {
150 $templateEvents[$template->templateName] = [];
153 $templateEvents[$template->templateName][] = $event;
158 foreach ($templateEvents as &$events) {
163 return $templateEvents;
166 $templateList = new TemplateList();
167 $templateList->getConditionBuilder()->add(
168 'template.packageID IN (?)',
170 [$this->installation->getPackage()->packageID],
171 array_keys($this->installation->getPackage()->getAllRequiredPackages())
174 $templateList->getConditionBuilder()->add('template.templateGroupID IS NULL');
175 $templateList->sqlOrderBy = 'template.templateName ASC';
176 $templateList->readObjects();
178 $templateEvents = $getEvents($templateList);
180 $acpTemplateList = new ACPTemplateList();
181 $acpTemplateList->getConditionBuilder()->add(
182 'acp_template.packageID IN (?)',
184 [$this->installation->getPackage()->packageID],
185 array_keys($this->installation->getPackage()->getAllRequiredPackages())
188 $acpTemplateList->sqlOrderBy = 'acp_template.templateName ASC';
189 $acpTemplateList->readObjects();
191 $acpTemplateEvents = $getEvents($acpTemplateList);
193 /** @var FormContainer $dataContainer */
194 $dataContainer = $form->getNodeById('data');
196 $dataContainer->appendChildren([
197 TextFormField::create('name')
198 ->label('wcf.acp.pip.templateListener.name')
199 ->description('wcf.acp.pip.templateListener.name.description')
201 ->addValidator(new FormFieldValidator('format', function(TextFormField $formField) {
202 if (!preg_match('~^[a-z][A-z]+$~', $formField->getValue())) {
203 $formField->addValidationError(
204 new FormFieldValidationError(
206 'wcf.acp.pip.templateListener.name.error.format'
212 SingleSelectionFormField::create('frontendTemplateName')
213 ->objectProperty('templatename')
214 ->label('wcf.acp.pip.templateListener.templateName')
215 ->description('wcf.acp.pip.templateListener.templateName.description')
217 ->options(array_combine(array_keys($templateEvents), array_keys($templateEvents)))
220 SingleSelectionFormField::create('acpTemplateName')
221 ->objectProperty('templatename')
222 ->label('wcf.acp.pip.templateListener.templateName')
223 ->description('wcf.acp.pip.templateListener.templateName.description')
225 ->options(array_combine(array_keys($acpTemplateEvents), array_keys($acpTemplateEvents)))
229 /** @var SingleSelectionFormField $frontendTemplateName */
230 $frontendTemplateName = $form->getNodeById('frontendTemplateName');
231 foreach ($templateEvents as $templateName => $events) {
232 $dataContainer->appendChild(
233 SingleSelectionFormField::create($templateName . '_eventName')
234 ->objectProperty('eventname')
235 ->label('wcf.acp.pip.templateListener.eventName')
236 ->description('wcf.acp.pip.templateListener.eventName.description')
238 ->options(array_combine($events, $events))
240 ValueFormFieldDependency::create('templateName')
241 ->field($frontendTemplateName)
242 ->values([$templateName])
247 /** @var SingleSelectionFormField $acpTemplateName */
248 $acpTemplateName = $form->getNodeById('acpTemplateName');
249 foreach ($acpTemplateEvents as $templateName => $events) {
250 $dataContainer->appendChild(
251 SingleSelectionFormField::create('acp_' . $templateName . '_eventName')
252 ->objectProperty('eventname')
253 ->label('wcf.acp.pip.templateListener.eventName')
254 ->description('wcf.acp.pip.templateListener.eventName.description')
256 ->options(array_combine($events, $events))
258 ValueFormFieldDependency::create('acpTemplateName')
259 ->field($acpTemplateName)
260 ->values([$templateName])
265 $dataContainer->appendChildren([
266 SingleSelectionFormField::create('environment')
267 ->label('wcf.acp.pip.templateListener.environment')
268 ->description('wcf.acp.pip.templateListener.environment.description')
275 ->addValidator(new FormFieldValidator('uniqueness', function(SingleSelectionFormField $formField) {
276 /** @var TextFormField $nameField */
277 $nameField = $formField->getDocument()->getNodeById('name');
279 /** @var SingleSelectionFormField $templateNameFormField */
280 $templateNameFormField = $formField->getDocument()->getNodeById('frontendTemplateName');
282 /** @var SingleSelectionFormField $acpTemplateNameFormField */
283 $acpTemplateNameFormField = $formField->getDocument()->getNodeById('acpTemplateName');
286 $formField->getDocument()->getFormMode() === IFormDocument::FORM_MODE_CREATE ||
287 $this->editedEntry->getAttribute('name') !== $nameField->getSaveValue() ||
288 $this->editedEntry->getElementsByTagName('environment')->item(0)->nodeValue !== $formField->getSaveValue() ||
290 $formField->getSaveValue() === 'admin' &&
291 $this->editedEntry->getElementsByTagName('templatename')->item(0)->nodeValue !== $acpTemplateNameFormField->getSaveValue()
294 $formField->getSaveValue() === 'user' &&
295 $this->editedEntry->getElementsByTagName('templatename')->item(0)->nodeValue !== $templateNameFormField->getSaveValue()
298 $listenerList = new TemplateListenerList();
299 $listenerList->getConditionBuilder()->add(
301 [$nameField->getSaveValue()]
304 if ($formField->getSaveValue() === 'admin') {
305 /** @var SingleSelectionFormField $templateNameField */
306 $templateNameField = $formField->getDocument()->getNodeById('acpTemplateName');
308 /** @var SingleSelectionFormField $eventNameField */
309 $eventNameField = $formField->getDocument()->getNodeById('acp_' . $templateNameField->getSaveValue() . '_eventName');
311 /** @var SingleSelectionFormField $templateNameField */
312 $templateNameField = $formField->getDocument()->getNodeById('frontendTemplateName');
314 /** @var SingleSelectionFormField $eventNameField */
315 $eventNameField = $formField->getDocument()->getNodeById($templateNameField->getSaveValue() . '_eventName');
318 $templateName = $templateNameField->getSaveValue();
319 $eventName = $eventNameField->getSaveValue();
321 $listenerList->getConditionBuilder()->add('templateName = ?', [$templateName]);
322 $listenerList->getConditionBuilder()->add('eventName = ?', [$eventName]);
323 $listenerList->getConditionBuilder()->add('environment = ?', [$formField->getSaveValue()]);
325 if ($listenerList->countObjects() > 0) {
326 $nameField->addValidationError(
327 new FormFieldValidationError(
329 'wcf.acp.pip.templateListener.name.error.notUnique'
336 MultilineTextFormField::create('templateCode')
337 ->objectProperty('templatecode')
338 ->label('wcf.acp.pip.templateListener.templateCode')
339 ->description('wcf.acp.pip.templateListener.templateCode.description')
342 IntegerFormField::create('niceValue')
343 ->objectProperty('nice')
344 ->label('wcf.acp.pip.templateListener.niceValue')
345 ->description('wcf.acp.pip.templateListener.niceValue.description')
350 OptionFormField::create()
351 ->description('wcf.acp.pip.templateListener.options.description')
352 ->packageIDs(array_merge(
353 [$this->installation->getPackage()->packageID],
354 array_keys($this->installation->getPackage()->getAllRequiredPackages())
357 UserGroupOptionFormField::create()
358 ->description('wcf.acp.pip.templateListener.permissions.description')
359 ->packageIDs(array_merge(
360 [$this->installation->getPackage()->packageID],
361 array_keys($this->installation->getPackage()->getAllRequiredPackages())
365 /** @var SingleSelectionFormField $environment */
366 $environment = $form->getNodeById('environment');
368 $form->getNodeById('frontendTemplateName')->addDependency(
369 ValueFormFieldDependency::create('environment')
370 ->field($environment)
373 $form->getNodeById('acpTemplateName')->addDependency(
374 ValueFormFieldDependency::create('environment')
375 ->field($environment)
379 // ensure proper normalization of template code
380 $form->getDataHandler()->add(new CustomFormFieldDataProcessor('templateCode', function(IFormDocument $document, array $parameters) {
381 $parameters['data']['templatecode'] = StringUtil::unifyNewlines(StringUtil::escapeCDATA($parameters['data']['templatecode']));
391 protected function fetchElementData(\DOMElement $element, $saveData) {
393 'environment' => $element->getElementsByTagName('environment')->item(0)->nodeValue,
394 'eventName' => $element->getElementsByTagName('eventname')->item(0)->nodeValue,
395 'name' => $element->getAttribute('name'),
396 'packageID' => $this->installation->getPackage()->packageID,
397 'templateCode' => $element->getElementsByTagName('templatecode')->item(0)->nodeValue,
398 'templateName' => $element->getElementsByTagName('templatename')->item(0)->nodeValue
401 $nice = $element->getElementsByTagName('nice')->item(0);
402 if ($nice !== null) {
403 $data['niceValue'] = $nice->nodeValue;
405 else if ($saveData) {
406 $data['niceValue'] = 0;
409 foreach (['options', 'permissions'] as $elementName) {
410 $optionalElement = $element->getElementsByTagName($elementName)->item(0);
411 if ($optionalElement !== null) {
412 $data[$elementName] = $optionalElement->nodeValue;
414 else if ($saveData) {
415 $data[$elementName] = '';
426 public function getElementIdentifier(\DOMElement $element) {
428 $element->getElementsByTagName('templatename')->item(0)->nodeValue . '/' .
429 $element->getElementsByTagName('eventname')->item(0)->nodeValue . '/' .
430 $element->getElementsByTagName('environment')->item(0)->nodeValue . '/' .
431 $element->getAttribute('name')
439 public function setEntryData($identifier, IFormDocument $document) {
440 if ($this->defaultSetEntryData($identifier, $document)) {
441 $data = $this->getElementData($this->getElementByIdentifier($this->getProjectXml(), $identifier));
443 switch ($data['environment']) {
445 /** @var SingleSelectionFormField $templateName */
446 $templateName = $document->getNodeById('acpTemplateName');
448 /** @var SingleSelectionFormField $eventName */
449 $eventName = $document->getNodeById('acp_' . $data['templateName'] . '_eventName');
453 /** @var SingleSelectionFormField $templateName */
454 $templateName = $document->getNodeById('frontendTemplateName');
456 /** @var SingleSelectionFormField $eventName */
457 $eventName = $document->getNodeById($data['templateName'] . '_eventName');
461 throw new \LogicException("Unknown enviornment '{$data['environment']}'.");
464 $templateName->value($data['templateName']);
465 $eventName->value($data['eventName']);
477 protected function setEntryListKeys(IDevtoolsPipEntryList $entryList) {
478 $entryList->setKeys([
479 'name' => 'wcf.acp.pip.templateListener.name',
480 'templateName' => 'wcf.acp.pip.templateListener.templateName',
481 'eventName' => 'wcf.acp.pip.templateListener.eventName',
482 'environment' => 'wcf.acp.pip.templateListener.environment'
490 protected function prepareXmlElement(\DOMDocument $document, IFormDocument $form) {
491 $data = $form->getData()['data'];
493 $listener = $document->createElement($this->tagName);
494 $listener->setAttribute('name', $data['name']);
496 $this->appendElementChildren(
519 protected function prepareDeleteXmlElement(\DOMElement $element) {
520 $templateListener = $element->ownerDocument->createElement($this->tagName);
521 $templateListener->setAttribute('name', $element->getAttribute('name'));
523 foreach (['environment', 'templatename', 'eventname'] as $childElement) {
524 $templateListener->appendChild($element->ownerDocument->createElement(
526 $element->getElementsByTagName($childElement)->item(0)->nodeValue
530 return $templateListener;
537 protected function deleteObject(\DOMElement $element) {
539 foreach (['environment', 'templatename', 'eventname'] as $childElement) {
540 $elements[$childElement] = $element->getElementsByTagName($childElement)->item(0)->nodeValue;
543 $this->handleDelete([[
544 'attributes' => ['name' => $element->getAttribute('name')],
545 'elements' => $elements