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\DevtoolsPipEntryList;
12 use wcf\system\devtools\pip\IDevtoolsPipEntryList;
13 use wcf\system\devtools\pip\IGuiPackageInstallationPlugin;
14 use wcf\system\devtools\pip\TXmlGuiPackageInstallationPlugin;
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;
23 use wcf\util\StringUtil;
26 * Installs, updates and deletes template listeners.
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
33 class TemplateListenerPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IGuiPackageInstallationPlugin {
34 use TXmlGuiPackageInstallationPlugin;
39 public $className = TemplateListenerEditor::class;
44 protected function handleDelete(array $items) {
45 $sql = "DELETE FROM wcf".WCF_N."_".$this->tableName."
50 AND templateName = ?";
51 $statement = WCF::getDB()->prepareStatement($sql);
52 foreach ($items as $item) {
54 $this->installation->getPackageID(),
55 $item['elements']['environment'],
56 $item['elements']['eventname'],
57 $item['attributes']['name'],
58 $item['elements']['templatename']
66 protected function prepareImport(array $data) {
67 $niceValue = isset($data['elements']['nice']) ? intval($data['elements']['nice']) : 0;
68 if ($niceValue < -128) {
71 else if ($niceValue > 127) {
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']
90 protected function findExistingItem(array $data) {
92 FROM wcf".WCF_N."_".$this->tableName."
99 $this->installation->getPackageID(),
101 $data['templateName'],
108 'parameters' => $parameters
115 protected function cleanup() {
116 // clear cache immediately
117 TemplateListenerCodeCacheBuilder::getInstance()->reset();
124 public static function getSyncDependencies() {
132 public function addFormFields(IFormDocument $form) {
133 $ldq = preg_quote(WCF::getTPL()->getCompiler()->getLeftDelimiter(), '~');
134 $rdq = preg_quote(WCF::getTPL()->getCompiler()->getRightDelimiter(), '~');
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;
143 foreach ($matches['event'] as $event) {
144 if (!isset($templateEvents[$template->templateName])) {
145 $templateEvents[$template->templateName] = [];
148 $templateEvents[$template->templateName][] = $event;
153 foreach ($templateEvents as &$events) {
158 return $templateEvents;
161 $templateList = new TemplateList();
162 $templateList->getConditionBuilder()->add(
163 'template.packageID IN (?)',
164 [array_keys($this->installation->getPackage()->getAllRequiredPackages())]
166 $templateList->getConditionBuilder()->add('template.templateGroupID IS NULL');
167 $templateList->sqlOrderBy = 'template.templateName ASC';
168 $templateList->readObjects();
170 $templateEvents = $getEvents($templateList);
172 $acpTemplateList = new ACPTemplateList();
173 $acpTemplateList->getConditionBuilder()->add(
174 'acp_template.packageID IN (?)',
175 [array_keys($this->installation->getPackage()->getAllRequiredPackages())]
177 $acpTemplateList->sqlOrderBy = 'acp_template.templateName ASC';
178 $acpTemplateList->readObjects();
180 $acpTemplateEvents = $getEvents($acpTemplateList);
182 $form->getNodeById('data')->appendChildren([
183 TextFormField::create('name')
184 ->label('wcf.acp.pip.templateListener.name')
185 ->description('wcf.acp.pip.templateListener.name.description')
187 ->addValidator(new FormFieldValidator('format', function(TextFormField $formField) {
188 if (!preg_match('~^[a-z][A-z]+$~', $formField->getValue())) {
189 $formField->addValidationError(
190 new FormFieldValidationError(
192 'wcf.acp.pip.templateListener.name.error.format'
198 SingleSelectionFormField::create('templateName')
199 ->objectProperty('templatename')
200 ->label('wcf.acp.pip.templateListener.templateName')
201 ->description('wcf.acp.pip.templateListener.templateName.description')
203 ->options(array_combine(array_keys($templateEvents), array_keys($templateEvents)))
206 SingleSelectionFormField::create('acpTemplateName')
207 ->objectProperty('templatename')
208 ->label('wcf.acp.pip.templateListener.templateName')
209 ->description('wcf.acp.pip.templateListener.templateName.description')
211 ->options(array_combine(array_keys($acpTemplateEvents), array_keys($acpTemplateEvents)))
215 foreach ($templateEvents as $templateName => $events) {
216 $form->getNodeById('data')->appendChild(
217 SingleSelectionFormField::create($templateName . '_eventName')
218 ->objectProperty('eventname')
219 ->label('wcf.acp.pip.templateListener.eventName')
220 ->description('wcf.acp.pip.templateListener.eventName.description')
222 ->options(array_combine($events, $events))
224 ValueFormFieldDependency::create('templateName')
225 ->field($form->getNodeById('templateName'))
226 ->values([$templateName])
231 foreach ($acpTemplateEvents as $templateName => $events) {
232 $form->getNodeById('data')->appendChild(
233 SingleSelectionFormField::create('acp_' . $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('acpTemplateName')
241 ->field($form->getNodeById('acpTemplateName'))
242 ->values([$templateName])
247 $form->getNodeById('data')->appendChildren([
248 SingleSelectionFormField::create('environment')
249 ->label('wcf.acp.pip.templateListener.environment')
250 ->description('wcf.acp.pip.templateListener.environment.description')
257 ->addValidator(new FormFieldValidator('uniqueness', function(SingleSelectionFormField $formField) {
258 $listenerList = new TemplateListenerList();
259 $listenerList->getConditionBuilder()->add(
261 [$formField->getDocument()->getNodeById('name')->getSaveValue()]
264 if ($formField->getSaveValue() === 'admin') {
265 $templateName = $formField->getDocument()->getNodeById('acpTemplateName')->getSaveValue();
266 $eventName = $formField->getDocument()->getNodeById('acp_' . $templateName . '_eventName')->getSaveValue();
269 $templateName = $formField->getDocument()->getNodeById('templateName')->getSaveValue();
270 $eventName = $formField->getDocument()->getNodeById($templateName . '_eventName')->getSaveValue();
273 $listenerList->getConditionBuilder()->add('templateName = ?', [$templateName]);
275 $listenerList->getConditionBuilder()->add('eventName = ?', [$eventName]);
276 $listenerList->getConditionBuilder()->add('environment = ?', [$formField->getSaveValue()]);
278 if ($listenerList->countObjects() > 0) {
279 $formField->getDocument()->getNodeById('name')->addValidationError(
280 new FormFieldValidationError(
282 'wcf.acp.pip.templateListener.name.error.notUnique'
288 // TODO: use field with code support
289 MultilineTextFormField::create('templateCode')
290 ->objectProperty('templatecode')
291 ->label('wcf.acp.pip.templateListener.templateCode')
292 ->description('wcf.acp.pip.templateListener.templateCode.description')
296 $form->getNodeById('templateName')->addDependency(
297 ValueFormFieldDependency::create('environment')
298 ->field($form->getNodeById('environment'))
301 $form->getNodeById('acpTemplateName')->addDependency(
302 ValueFormFieldDependency::create('environment')
303 ->field($form->getNodeById('environment'))
312 protected function getElementData(\DOMElement $element, bool $saveData = false): array {
314 'environment' => $element->getElementsByTagName('environment')->item(0)->nodeValue,
315 'eventName' => $element->getElementsByTagName('eventname')->item(0)->nodeValue,
316 'name' => $element->getAttribute('name'),
317 'packageID' => $this->installation->getPackage()->packageID,
318 'templateCode' => $element->getElementsByTagName('templatecode')->item(0)->nodeValue,
319 'templateName' => $element->getElementsByTagName('templatename')->item(0)->nodeValue
327 public function getElementIdentifier(\DOMElement $element): string {
329 $element->getElementsByTagName('templatename')->item(0)->nodeValue . '/' .
330 $element->getElementsByTagName('eventname')->item(0)->nodeValue . '/' .
331 $element->getElementsByTagName('environment')->item(0)->nodeValue . '/' .
332 $element->getAttribute('name')
340 protected function setEntryListKeys(IDevtoolsPipEntryList $entryList) {
341 $entryList->setKeys([
342 'name' => 'wcf.acp.pip.templateListener.name',
343 'templateName' => 'wcf.acp.pip.templateListener.templateName',
344 'eventName' => 'wcf.acp.pip.templateListener.eventName',
345 'environment' => 'wcf.acp.pip.templateListener.environment'
353 protected function sortDocument(\DOMDocument $document) {
354 $this->sortImportDelete($document);
356 $compareFunction = function(\DOMElement $element1, \DOMElement $element2) {
357 $templateName1 = $element1->getElementsByTagName('templatename')->item(0)->nodeValue;
358 $templateName2 = $element2->getElementsByTagName('templatename')->item(0)->nodeValue;
360 if ($templateName1 !== $templateName2) {
361 return strcmp($templateName1, $templateName2);
364 $eventName1 = $element1->getElementsByTagName('eventname')->item(0)->nodeValue;
365 $eventName2 = $element2->getElementsByTagName('eventname')->item(0)->nodeValue;
367 if ($eventName1 !== $eventName2) {
368 return strcmp($eventName1, $eventName2);
372 $element1->getElementsByTagName('environment')->item(0)->nodeValue,
373 $element2->getElementsByTagName('environment')->item(0)->nodeValue
377 $this->sortChildNodes($document->getElementsByTagName('import'), $compareFunction);
378 $this->sortChildNodes($document->getElementsByTagName('delete'), $compareFunction);
385 protected function writeEntry(\DOMDocument $document, IFormDocument $form): \DOMElement {
386 $listener = $document->createElement($this->tagName);
387 $listener->setAttribute('name', $form->getNodeById('name')->getSaveValue());
389 $environment = $form->getNodeById('environment')->getSaveValue();
390 if ($environment === 'user') {
391 $templateName = $form->getNodeById('templateName')->getSaveValue();
393 $listener->appendChild($document->createElement('templatename', $templateName));
394 $listener->appendChild($document->createElement('eventname', $form->getNodeById($templateName . '_eventName')->getSaveValue()));
397 $templateName = $form->getNodeById('acpTemplateName')->getSaveValue();
399 $listener->appendChild($document->createElement('templatename', $templateName));
400 $listener->appendChild($document->createElement('eventname', $form->getNodeById('acp_' . $templateName . '_eventName')->getSaveValue()));
402 $listener->appendChild($document->createElement('templatecode', '<![CDATA[' . StringUtil::unifyNewlines(StringUtil::escapeCDATA($form->getNodeById('templateCode')->getSaveValue())) . ']]>'));
403 $listener->appendChild($document->createElement('environment', $environment));
405 $document->getElementsByTagName('import')->item(0)->appendChild($listener);