Resolve language item-related PIP GUI todos
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / package / plugin / BoxPackageInstallationPlugin.class.php
1 <?php
2 namespace wcf\system\package\plugin;
3 use wcf\data\box\Box;
4 use wcf\data\box\BoxEditor;
5 use wcf\data\box\BoxList;
6 use wcf\data\object\type\ObjectTypeCache;
7 use wcf\data\page\PageNode;
8 use wcf\data\page\PageNodeTree;
9 use wcf\system\box\AbstractDatabaseObjectListBoxController;
10 use wcf\system\database\util\PreparedStatementConditionBuilder;
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\exception\SystemException;
15 use wcf\system\form\builder\container\FormContainer;
16 use wcf\system\form\builder\container\TabFormContainer;
17 use wcf\system\form\builder\container\TabMenuFormContainer;
18 use wcf\system\form\builder\field\BooleanFormField;
19 use wcf\system\form\builder\field\dependency\ValueFormFieldDependency;
20 use wcf\system\form\builder\field\ItemListFormField;
21 use wcf\system\form\builder\field\MultilineTextFormField;
22 use wcf\system\form\builder\field\MultipleSelectionFormField;
23 use wcf\system\form\builder\field\RadioButtonFormField;
24 use wcf\system\form\builder\field\SingleSelectionFormField;
25 use wcf\system\form\builder\field\TextFormField;
26 use wcf\system\form\builder\field\TitleFormField;
27 use wcf\system\form\builder\field\validation\FormFieldValidationError;
28 use wcf\system\form\builder\field\validation\FormFieldValidator;
29 use wcf\system\form\builder\field\validation\FormFieldValidatorUtil;
30 use wcf\system\form\builder\IFormDocument;
31 use wcf\system\language\LanguageFactory;
32 use wcf\system\WCF;
33 use wcf\util\StringUtil;
34
35 /**
36 * Installs, updates and deletes boxes.
37 *
38 * @author Alexander Ebert, Matthias Schmidt
39 * @copyright 2001-2018 WoltLab GmbH
40 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
41 * @package WoltLabSuite\Core\Acp\Package\Plugin
42 * @since 3.0
43 */
44 class BoxPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IGuiPackageInstallationPlugin {
45 use TXmlGuiPackageInstallationPlugin;
46
47 /**
48 * @inheritDoc
49 */
50 public $className = BoxEditor::class;
51
52 /**
53 * list of created or updated boxes by id
54 * @var BoxEditor[]
55 */
56 protected $boxes = [];
57
58 /**
59 * box contents
60 * @var array
61 */
62 protected $content = [];
63
64 /**
65 * list of element names which are not considered as additional data
66 * @var string[]
67 */
68 public static $reservedTags = ['boxType', 'content', 'cssClassName', 'name', 'objectType', 'position', 'showHeader', 'visibilityExceptions', 'visibleEverywhere'];
69
70 /**
71 * @inheritDoc
72 */
73 public $tagName = 'box';
74
75 /**
76 * visibility exceptions per box
77 * @var string[]
78 */
79 public $visibilityExceptions = [];
80
81 /**
82 * @inheritDoc
83 */
84 protected function handleDelete(array $items) {
85 $sql = "DELETE FROM wcf".WCF_N."_box
86 WHERE identifier = ?
87 AND packageID = ?";
88 $statement = WCF::getDB()->prepareStatement($sql);
89
90 WCF::getDB()->beginTransaction();
91 foreach ($items as $item) {
92 $statement->execute([
93 $item['attributes']['identifier'],
94 $this->installation->getPackageID()
95 ]);
96 }
97 WCF::getDB()->commitTransaction();
98 }
99
100 /**
101 * @inheritDoc
102 * @throws SystemException
103 */
104 protected function getElement(\DOMXPath $xpath, array &$elements, \DOMElement $element) {
105 $nodeValue = $element->nodeValue;
106
107 if ($element->tagName === 'name') {
108 if (empty($element->getAttribute('language'))) {
109 throw new SystemException("Missing required attribute 'language' for '" . $element->tagName . "' element (box '" . $element->parentNode->getAttribute('identifier') . "')");
110 }
111
112 // element can occur multiple times using the `language` attribute
113 if (!isset($elements[$element->tagName])) $elements[$element->tagName] = [];
114
115 $elements[$element->tagName][$element->getAttribute('language')] = $element->nodeValue;
116 }
117 else if ($element->tagName === 'content') {
118 // content can occur multiple times using the `language` attribute
119 if (!isset($elements['content'])) $elements['content'] = [];
120
121 $children = [];
122 /** @var \DOMElement $child */
123 foreach ($xpath->query('child::*', $element) as $child) {
124 $children[$child->tagName] = $child->nodeValue;
125 }
126
127 if (empty($children['title'])) {
128 throw new SystemException("Expected non-empty child element 'title' for 'content' element (box '" . $element->parentNode->getAttribute('identifier') . "')");
129 }
130
131 $elements['content'][$element->getAttribute('language')] = [
132 'content' => isset($children['content']) ? $children['content'] : '',
133 'title' => $children['title']
134 ];
135 }
136 else if ($element->tagName === 'visibilityExceptions') {
137 $elements['visibilityExceptions'] = [];
138 /** @var \DOMElement $child */
139 foreach ($xpath->query('child::*', $element) as $child) {
140 $elements['visibilityExceptions'][] = $child->nodeValue;
141 }
142 }
143 else {
144 $elements[$element->tagName] = $nodeValue;
145 }
146 }
147
148 /**
149 * @inheritDoc
150 * @throws SystemException
151 */
152 protected function prepareImport(array $data) {
153 $content = [];
154 $boxType = $data['elements']['boxType'];
155 $objectTypeID = null;
156 $identifier = $data['attributes']['identifier'];
157 $isMultilingual = false;
158 $position = $data['elements']['position'];
159
160 if (!in_array($position, ['bottom', 'contentBottom', 'contentTop', 'footer', 'footerBoxes', 'headerBoxes', 'hero', 'sidebarLeft', 'sidebarRight', 'top'])) {
161 throw new SystemException("Unknown box position '{$position}' for box '{$identifier}'");
162 }
163
164 // pick the display name by choosing the default language, or 'en' or '' (empty string)
165 $defaultLanguageCode = LanguageFactory::getInstance()->getDefaultLanguage()->getFixedLanguageCode();
166 if (isset($data['elements']['name'][$defaultLanguageCode])) {
167 // use the default language
168 $name = $data['elements']['name'][$defaultLanguageCode];
169 }
170 else if (isset($data['elements']['name']['en'])) {
171 // use the value for English
172 $name = $data['elements']['name']['en'];
173 }
174 else {
175 // fallback to the display name without/empty language attribute
176 $name = $data['elements']['name'][''];
177 }
178
179 switch ($boxType) {
180 /** @noinspection PhpMissingBreakStatementInspection */
181 case 'system':
182 if (empty($data['elements']['objectType'])) {
183 throw new SystemException("Missing required element 'objectType' for 'system'-type box '{$identifier}'");
184 }
185
186 $sql = "SELECT objectTypeID
187 FROM wcf".WCF_N."_object_type object_type
188 LEFT JOIN wcf".WCF_N."_object_type_definition object_type_definition
189 ON (object_type_definition.definitionID = object_type.definitionID)
190 WHERE objectType = ?
191 AND definitionName = ?";
192 $statement = WCF::getDB()->prepareStatement($sql);
193 $statement->execute([$data['elements']['objectType'], 'com.woltlab.wcf.boxController']);
194 $objectTypeID = $statement->fetchSingleColumn();
195 if (!$objectTypeID) {
196 throw new SystemException("Unknown object type '{$data['elements']['objectType']}' for 'system'-type box '{$identifier}'");
197 }
198
199 $isMultilingual = true;
200
201 // fallthrough
202
203 case 'html':
204 case 'text':
205 case 'tpl':
206 if (empty($data['elements']['content'])) {
207 if ($boxType === 'system') {
208 break;
209 }
210
211 throw new SystemException("Missing required 'content' element(s) for box '{$identifier}'");
212 }
213
214 if (count($data['elements']['content']) === 1) {
215 if (!isset($data['elements']['content'][''])) {
216 throw new SystemException("Expected one 'content' element without a 'language' attribute for box '{$identifier}'");
217 }
218 }
219 else {
220 $isMultilingual = true;
221
222 if (isset($data['elements']['content'][''])) {
223 throw new SystemException("Cannot mix 'content' elements with and without 'language' attribute for box '{$identifier}'");
224 }
225 }
226
227 $content = $data['elements']['content'];
228
229 break;
230
231 default:
232 throw new SystemException("Unknown type '{$boxType}' for box '{$identifier}");
233 break;
234 }
235
236 if (!empty($data['elements']['visibilityExceptions'])) {
237 $this->visibilityExceptions[$identifier] = $data['elements']['visibilityExceptions'];
238 }
239
240 $additionalData = [];
241 foreach ($data['elements'] as $tagName => $nodeValue) {
242 if (!in_array($tagName, self::$reservedTags)) {
243 $additionalData[$tagName] = $nodeValue;
244 }
245 }
246
247 return [
248 'identifier' => $identifier,
249 'content' => $content,
250 'name' => $name,
251 'boxType' => $boxType,
252 'position' => $position,
253 'showOrder' => $this->getItemOrder($position),
254 'visibleEverywhere' => (!empty($data['elements']['visibleEverywhere'])) ? 1 : 0,
255 'isMultilingual' => $isMultilingual ? '1' : '0',
256 'cssClassName' => (!empty($data['elements']['cssClassName'])) ? $data['elements']['cssClassName'] : '',
257 'showHeader' => (!empty($data['elements']['showHeader'])) ? 1 : 0,
258 'originIsSystem' => 1,
259 'objectTypeID' => $objectTypeID,
260 'additionalData' => serialize($additionalData)
261 ];
262 }
263
264 /**
265 * @inheritDoc
266 */
267 protected function findExistingItem(array $data) {
268 $sql = "SELECT *
269 FROM wcf".WCF_N."_box
270 WHERE identifier = ?
271 AND packageID = ?";
272 $parameters = [
273 $data['identifier'],
274 $this->installation->getPackageID()
275 ];
276
277 return [
278 'sql' => $sql,
279 'parameters' => $parameters
280 ];
281 }
282
283 /**
284 * Returns the show order for a new item that will append it to the current
285 * menu or parent item.
286 *
287 * @param string $position box position
288 * @return integer
289 */
290 protected function getItemOrder($position) {
291 $sql = "SELECT MAX(showOrder) AS showOrder
292 FROM wcf".WCF_N."_box
293 WHERE position = ?";
294 $statement = WCF::getDB()->prepareStatement($sql, 1);
295 $statement->execute([$position]);
296
297 $row = $statement->fetchSingleRow();
298
299 return (!$row['showOrder']) ? 1 : $row['showOrder'] + 1;
300 }
301
302 /**
303 * @inheritDoc
304 */
305 protected function import(array $row, array $data) {
306 // extract content
307 $content = $data['content'];
308 unset($data['content']);
309
310 // updating boxes is only supported for 'system' type boxes, all other
311 // types would potentially overwrite changes made by the user if updated
312 if (!empty($row) && $row['boxType'] !== 'system') {
313 $box = new Box(null, $row);
314 }
315 else {
316 $box = parent::import($row, $data);
317 }
318
319 // store content for later import
320 $this->content[$box->boxID] = $content;
321 $this->boxes[$box->boxID] = ($box instanceof Box) ? new BoxEditor($box) : $box;
322
323 return $box;
324 }
325
326 /**
327 * @inheritDoc
328 */
329 protected function postImport() {
330 if (!empty($this->content)) {
331 $sql = "SELECT COUNT(*) AS count
332 FROM wcf".WCF_N."_box_content
333 WHERE boxID = ?
334 AND languageID IS NULL";
335 $statement = WCF::getDB()->prepareStatement($sql);
336
337 $sql = "INSERT IGNORE INTO wcf".WCF_N."_box_content
338 (boxID, languageID, title, content)
339 VALUES (?, ?, ?, ?)";
340 $insertStatement = WCF::getDB()->prepareStatement($sql);
341
342 WCF::getDB()->beginTransaction();
343 foreach ($this->content as $boxID => $contentData) {
344 $boxEditor = $this->boxes[$boxID];
345
346 // expand non-i18n value
347 if ($boxEditor->boxType === 'system' && count($contentData) === 1 && isset($contentData[''])) {
348 foreach (LanguageFactory::getInstance()->getLanguages() as $language) {
349 $insertStatement->execute([
350 $boxID,
351 $language->languageID,
352 $contentData['']['title'],
353 ''
354 ]);
355 }
356
357 continue;
358 }
359
360 foreach ($contentData as $languageCode => $content) {
361 $languageID = null;
362 if ($languageCode != '') {
363 $language = LanguageFactory::getInstance()->getLanguageByCode($languageCode);
364 if ($language === null) continue;
365
366 $languageID = $language->languageID;
367 }
368
369 if ($languageID === null) {
370 $statement->execute([$boxID]);
371 if ($statement->fetchSingleColumn()) continue;
372 }
373
374 $boxContent = isset($content['content']) ? $content['content'] : '';
375 $insertStatement->execute([
376 $boxID,
377 $languageID,
378 $content['title'],
379 $boxContent
380 ]);
381
382 if ($boxEditor->getDecoratedObject()->boxType === 'tpl') {
383 $boxEditor->writeTemplate($languageID, $boxContent);
384 }
385 }
386 }
387 WCF::getDB()->commitTransaction();
388 }
389
390 if (empty($this->visibilityExceptions)) return;
391
392 // get all boxes belonging to the identifiers
393 $conditions = new PreparedStatementConditionBuilder();
394 $conditions->add("identifier IN (?)", [array_keys($this->visibilityExceptions)]);
395 $conditions->add("packageID = ?", [$this->installation->getPackageID()]);
396
397 $sql = "SELECT *
398 FROM wcf".WCF_N."_box
399 ".$conditions;
400 $statement = WCF::getDB()->prepareStatement($sql);
401 $statement->execute($conditions->getParameters());
402
403 /** @var Box[] $boxes */
404 $boxes = $statement->fetchObjects(Box::class, 'identifier');
405
406 // save visibility exceptions
407 $sql = "DELETE FROM wcf".WCF_N."_box_to_page
408 WHERE boxID = ?";
409 $deleteStatement = WCF::getDB()->prepareStatement($sql);
410 $sql = "INSERT IGNORE wcf".WCF_N."_box_to_page
411 (boxID, pageID, visible)
412 VALUES (?, ?, ?)";
413 $insertStatement = WCF::getDB()->prepareStatement($sql);
414 foreach ($this->visibilityExceptions as $boxIdentifier => $pages) {
415 // delete old visibility exceptions
416 $deleteStatement->execute([$boxes[$boxIdentifier]->boxID]);
417
418 // get page ids
419 $conditionBuilder = new PreparedStatementConditionBuilder();
420 $conditionBuilder->add('identifier IN (?)', [$pages]);
421 $sql = "SELECT pageID
422 FROM wcf".WCF_N."_page
423 ".$conditionBuilder;
424 $statement = WCF::getDB()->prepareStatement($sql);
425 $statement->execute($conditionBuilder->getParameters());
426 $pageIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
427
428 // save page ids
429 foreach ($pageIDs as $pageID) {
430 $insertStatement->execute([$boxes[$boxIdentifier]->boxID, $pageID, $boxes[$boxIdentifier]->visibleEverywhere ? 0 : 1]);
431 }
432 }
433 }
434
435 /**
436 * @inheritDoc
437 * @since 3.1
438 */
439 public static function getSyncDependencies() {
440 return ['language', 'objectType'];
441 }
442
443 /**
444 * @inheritDoc
445 * @since 3.2
446 */
447 public function getAdditionalTemplateCode() {
448 return WCF::getTPL()->fetch('__boxPipGui');
449 }
450
451 /**
452 * @inheritDoc
453 * @since 3.2
454 */
455 protected function addFormFields(IFormDocument $form) {
456 $tabContainter = TabMenuFormContainer::create('tabMenu');
457 $form->appendChild($tabContainter);
458
459 $dataTab = TabFormContainer::create('dataTab')
460 ->label('wcf.global.form.data');
461 $tabContainter->appendChild($dataTab);
462 $dataContainer = FormContainer::create('dataTabData');
463 $dataTab->appendChild($dataContainer);
464
465 $contentTab = TabFormContainer::create('contentTab')
466 ->label('wcf.acp.pip.box.content');
467 $tabContainter->appendChild($contentTab);
468 $contentContainer = FormContainer::create('contentTabContent');
469 $contentTab->appendChild($contentContainer);
470
471 $dataContainer->appendChildren([
472 TextFormField::create('identifier')
473 ->label('wcf.acp.pip.box.identifier')
474 ->description('wcf.acp.pip.box.identifier.description')
475 ->required()
476 ->addValidator(FormFieldValidatorUtil::getDotSeparatedStringValidator(
477 'wcf.acp.pip.box.identifier',
478 4
479 ))
480 ->addValidator(new FormFieldValidator('uniqueness', function(TextFormField $formField) {
481 if (
482 $formField->getDocument()->getFormMode() === IFormDocument::FORM_MODE_CREATE ||
483 $this->editedEntry->getAttribute('identifier') !== $formField->getValue()
484 ) {
485 $pageList = new BoxList();
486 $pageList->getConditionBuilder()->add('identifier = ?', [$formField->getValue()]);
487
488 if ($pageList->countObjects() > 0) {
489 $formField->addValidationError(
490 new FormFieldValidationError(
491 'notUnique',
492 'wcf.acp.pip.box.identifier.error.notUnique'
493 )
494 );
495 }
496 }
497 })),
498
499 TextFormField::create('name')
500 ->label('wcf.acp.pip.box.name')
501 ->description('wcf.acp.pip.box.name.description')
502 ->required()
503 ->i18n()
504 ->i18nRequired()
505 ->languageItemPattern('__NONE__'),
506
507 RadioButtonFormField::create('boxType')
508 ->label('wcf.acp.pip.box.boxType')
509 ->description('wcf.acp.pip.box.boxType.description')
510 ->options(array_combine(Box::$availableBoxTypes, Box::$availableBoxTypes))
511 ->value('text'),
512
513 SingleSelectionFormField::create('objectType')
514 ->label('wcf.acp.pip.box.objectType')
515 ->description('wcf.acp.pip.box.objectType.description')
516 ->required()
517 ->options(function() {
518 $objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.boxController');
519
520 $options = [];
521 foreach ($objectTypes as $objectType) {
522 $options[$objectType->objectType] = $objectType->objectType;
523 }
524
525 asort($options);
526
527 return $options;
528 }),
529
530 SingleSelectionFormField::create('position')
531 ->label('wcf.acp.pip.box.position')
532 ->options(array_combine(Box::$availablePositions, Box::$availablePositions)),
533
534 BooleanFormField::create('showHeader')
535 ->label('wcf.acp.pip.box.showHeader')
536 ->value(true),
537
538 BooleanFormField::create('visibleEverywhere')
539 ->label('wcf.acp.pip.box.visibleEverywhere')
540 ->value(true),
541
542 MultipleSelectionFormField::create('visibilityExceptions')
543 ->label('wcf.acp.pip.box.visibilityExceptions.hiddenEverywhere')
544 ->filterable()
545 ->options(function() {
546 $pageNodeList = (new PageNodeTree())->getNodeList();
547
548 $nestedOptions = [];
549 /** @var PageNode $pageNode */
550 foreach ($pageNodeList as $pageNode) {
551 $nestedOptions[] = [
552 'depth' => $pageNode->getDepth() - 1,
553 'label' => $pageNode->name,
554 'value' => $pageNode->identifier
555 ];
556 }
557
558 return $nestedOptions;
559 }, true),
560
561 ItemListFormField::create('cssClassName')
562 ->label('wcf.acp.pip.box.cssClassName')
563 ->description('wcf.acp.pip.box.cssClassName.description')
564 ->saveValueType(ItemListFormField::SAVE_VALUE_TYPE_SSV)
565 ]);
566
567 $contentContainer->appendChildren([
568 TitleFormField::create('title')
569 ->label('wcf.acp.pip.box.content.title')
570 ->required()
571 ->i18n()
572 ->i18nRequired()
573 ->languageItemPattern('__NONE__'),
574
575 MultilineTextFormField::create('contentContent')
576 ->objectProperty('content')
577 ->label('wcf.acp.pip.box.content.content')
578 ->required()
579 ->i18n()
580 ->i18nRequired()
581 ->languageItemPattern('__NONE__')
582 ]);
583
584 // add box controller-specific form fields
585 foreach (ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.boxController') as $objectType) {
586 if (is_subclass_of($objectType->className, AbstractDatabaseObjectListBoxController::class)) {
587 /** @var AbstractDatabaseObjectListBoxController $boxController */
588 $boxController = new $objectType->className;
589
590 $boxController->addPipGuiFormFields($form, $objectType->objectType);
591 }
592 }
593
594 // add dependencies
595
596 /** @var SingleSelectionFormField $boxType */
597 $boxType = $dataContainer->getNodeById('boxType');
598
599 $dataContainer->getNodeById('objectType')->addDependency(
600 ValueFormFieldDependency::create('boxType')
601 ->field($boxType)
602 ->values(['system'])
603 );
604
605 $contentContainer->getNodeById('contentContent')->addDependency(
606 ValueFormFieldDependency::create('pageType')
607 ->field($boxType)
608 ->values(['system'])
609 ->negate()
610 );
611 }
612
613 /**
614 * @inheritDoc
615 * @since 3.2
616 */
617 protected function doGetElementData(\DOMElement $element, $saveData) {
618 $data = [
619 'boxType' => $element->getElementsByTagName('boxType')->item(0)->nodeValue,
620 'content' => [],
621 'identifier' => $element->getAttribute('identifier'),
622 'name' => [],
623 'originIsSystem' => 1,
624 'packageID' => $this->installation->getPackageID(),
625 'position' => $element->getElementsByTagName('position')->item(0)->nodeValue,
626 'title' => []
627 ];
628
629 /** @var \DOMElement $name */
630 foreach ($element->getElementsByTagName('name') as $name) {
631 $data['name'][LanguageFactory::getInstance()->getLanguageByCode($name->getAttribute('language'))->languageID] = $name->nodeValue;
632 }
633
634 /** @var \DOMElement $content */
635 foreach ($element->getElementsByTagName('content') as $content) {
636 if ($content->parentNode === $element) {
637 $languageID = LanguageFactory::getInstance()->getLanguageByCode($content->getAttribute('language'))->languageID;
638
639 $contentContent = $content->getElementsByTagName('content')->item(0);
640 if ($contentContent !== null) {
641 $data['content'][$languageID] = $contentContent->nodeValue;
642 }
643
644 $title = $content->getElementsByTagName('title')->item(0);
645 if ($title !== null) {
646 $data['title'][$languageID] = $title->nodeValue;
647 }
648 }
649 }
650
651 foreach (['objectType', 'cssClassName', 'showHeader', 'visibleEverywhere'] as $optionalElementName) {
652 $optionalElement = $element->getElementsByTagName($optionalElementName)->item(0);
653 if ($optionalElement !== null) {
654 $data[$optionalElementName] = $optionalElement->nodeValue;
655 }
656 }
657
658 $visibilityExceptions = $element->getElementsByTagName('visibilityExceptions')->item(0);
659 if ($visibilityExceptions !== null) {
660 $exceptions = [];
661 /** @var \DOMElement $page */
662 foreach ($visibilityExceptions->getElementsByTagName('page') as $page) {
663 $exceptions[] = $page->nodeValue;
664 }
665
666 if (!empty($exceptions)) {
667 $data['visibilityExceptions'] = $exceptions;
668 }
669 }
670
671 $objectTypeData = null;
672 if (isset($data['objectType'])) {
673 $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.boxController', $data['objectType']);
674 if ($objectType !== null && is_subclass_of($objectType->className, AbstractDatabaseObjectListBoxController::class)) {
675 /** @var AbstractDatabaseObjectListBoxController $boxController */
676 $boxController = new $objectType->className;
677
678 $objectTypeData = $boxController->getPipGuiElementData($element, $saveData);
679 }
680 }
681
682 if ($objectTypeData !== null) {
683 if ($saveData) {
684 $data['additionalData'] = serialize($objectTypeData);
685 }
686 else {
687 $data = array_merge($objectTypeData, $data);
688 }
689 }
690
691 if ($saveData) {
692 $defaultLanguageID = LanguageFactory::getInstance()->getDefaultLanguage()->languageID;
693 $englishLanguage = LanguageFactory::getInstance()->getLanguageByCode('en');
694
695 if (isset($data['name'][$defaultLanguageID])) {
696 // use the default language
697 $name = $data['name'][$defaultLanguageID];
698 }
699 else if ($englishLanguage !== null && isset($data['name'][$englishLanguage->languageID])) {
700 // use the value for English
701 $name = $data['name'][$englishLanguage->languageID];
702 }
703 else {
704 // fallback to first element
705 $name = reset($data['name']);
706 }
707
708 $data['name'] = $name;
709
710 if (isset($data['objectType'])) {
711 $objectType = $data['objectType'];
712 unset($data['objectType']);
713
714 $data['objectTypeID'] = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.boxController', $objectType)->objectTypeID;
715 }
716
717 if (isset($data['visibilityExceptions'])) {
718 $this->visibilityExceptions[$data['identifier']] = $data['visibilityExceptions'];
719 unset($data['visibilityExceptions']);
720 }
721
722 $content = [];
723 if (isset($data['content'])) {
724 $content['content'] = $data['content'];
725 unset($data['content']);
726 }
727 if (isset($data['title'])) {
728 $content['title'] = $data['title'];
729 unset($data['title']);
730 }
731
732 if (!empty($content)) {
733 $data['content'] = $content;
734 }
735 }
736
737 return $data;
738 }
739
740 /**
741 * @inheritDoc
742 * @since 3.2
743 */
744 public function getElementIdentifier(\DOMElement $element) {
745 return $element->getAttribute('identifier');
746 }
747
748 /**
749 * @inheritDoc
750 * @since 3.2
751 */
752 protected function setEntryListKeys(IDevtoolsPipEntryList $entryList) {
753 $entryList->setKeys([
754 'identifier' => 'wcf.acp.pip.box.identifier',
755 'boxType' => 'wcf.acp.pip.box.boxType'
756 ]);
757 }
758
759 /**
760 * @inheritDoc
761 * @since 3.2
762 */
763 protected function doCreateXmlElement(\DOMDocument $document, IFormDocument $form) {
764 $formData = $form->getData();
765 $data = $formData['data'];
766
767 if ($data['identifier'] === 'com.woltlab.wcf.MainMenu') {
768 $data['boxPosition'] = 'mainMenu';
769 }
770
771 $box = $document->createElement($this->tagName);
772 $box->setAttribute('identifier', $data['identifier']);
773
774 foreach ($formData['name_i18n'] as $languageID => $name) {
775 $nameElement = $document->createElement('name', $this->getAutoCdataValue($name));
776 $nameElement->setAttribute('language', LanguageFactory::getInstance()->getLanguage($languageID)->languageCode);
777
778 $box->appendChild($nameElement);
779 }
780
781 $box->appendChild($document->createElement('boxType', $data['boxType']));
782 $box->appendChild($document->createElement('position', $data['position']));
783
784 $this->appendElementChildren(
785 $box,
786 [
787 'objectType' => '',
788 'cssClassName' => '',
789 'showHeader' => 0
790 ],
791 $form
792 );
793
794 if (!empty($data['visibilityExceptions'])) {
795 $box->appendChild($document->createElement('visibleEverywhere', (string)($data['visibleEverywhere'] ?? 0)));
796
797 $visibilityExceptions = $document->createElement('visibilityExceptions');
798
799 sort($data['visibilityExceptions']);
800 foreach ($data['visibilityExceptions'] as $page) {
801 $visibilityExceptions->appendChild($document->createElement('page', $page));
802 }
803
804 $box->appendChild($visibilityExceptions);
805 }
806 else if (!empty($data['visibleEverywhere'])) {
807 $box->appendChild($document->createElement('visibleEverywhere', (string)$data['visibleEverywhere']));
808 }
809
810 foreach (LanguageFactory::getInstance()->getLanguages() as $language) {
811 $content = null;
812
813 foreach (['title', 'content'] as $property) {
814 if (!empty($formData[$property . '_i18n'][$language->languageID])) {
815 if ($content === null) {
816 $content = $document->createElement('content');
817 $content->setAttribute('language', $language->languageCode);
818
819 $box->appendChild($content);
820 }
821
822 if ($property === 'content') {
823 $contentContent = $document->createElement('content');
824 $contentContent->appendChild(
825 $document->createCDATASection(
826 StringUtil::escapeCDATA(StringUtil::unifyNewlines(
827 $formData[$property . '_i18n'][$language->languageID]
828 ))
829 )
830 );
831
832 $content->appendChild($contentContent);
833 }
834 else {
835 $content->appendChild(
836 $document->createElement(
837 $property,
838 $formData[$property . '_i18n'][$language->languageID]
839 )
840 );
841 }
842 }
843 }
844 }
845
846 if (isset($data['objectType'])) {
847 $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.boxController', $data['objectType']);
848 if ($objectType !== null && is_subclass_of($objectType->className, AbstractDatabaseObjectListBoxController::class)) {
849 /** @var AbstractDatabaseObjectListBoxController $boxController */
850 $boxController = new $objectType->className;
851
852 $boxController->writePipGuiEntry($box, $form);
853 }
854 }
855
856 return $box;
857 }
858 }