2 namespace wcf\system\package\plugin
;
3 use wcf\data\language\Language
;
4 use wcf\data\package\PackageCache
;
5 use wcf\data\page\Page
;
6 use wcf\data\page\PageAction
;
7 use wcf\data\page\PageEditor
;
8 use wcf\data\page\PageList
;
9 use wcf\data\page\PageNode
;
10 use wcf\data\page\PageNodeTree
;
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\exception\SystemException
;
16 use wcf\system\form\builder\container\FormContainer
;
17 use wcf\system\form\builder\container\TabFormContainer
;
18 use wcf\system\form\builder\container\TabMenuFormContainer
;
19 use wcf\system\form\builder\field\BooleanFormField
;
20 use wcf\system\form\builder\field\ClassNameFormField
;
21 use wcf\system\form\builder\field\dependency\ValueFormFieldDependency
;
22 use wcf\system\form\builder\field\ItemListFormField
;
23 use wcf\system\form\builder\field\MultilineTextFormField
;
24 use wcf\system\form\builder\field\option\OptionFormField
;
25 use wcf\system\form\builder\field\RadioButtonFormField
;
26 use wcf\system\form\builder\field\SingleSelectionFormField
;
27 use wcf\system\form\builder\field\TextFormField
;
28 use wcf\system\form\builder\field\TitleFormField
;
29 use wcf\system\form\builder\field\user\group\option\UserGroupOptionFormField
;
30 use wcf\system\form\builder\field\validation\FormFieldValidationError
;
31 use wcf\system\form\builder\field\validation\FormFieldValidator
;
32 use wcf\system\form\builder\field\validation\FormFieldValidatorUtil
;
33 use wcf\system\form\builder\IFormDocument
;
34 use wcf\system\language\LanguageFactory
;
35 use wcf\system\page\handler\IMenuPageHandler
;
36 use wcf\system\request\RouteHandler
;
37 use wcf\system\search\SearchIndexManager
;
39 use wcf\util\StringUtil
;
42 * Installs, updates and deletes CMS pages.
44 * @author Alexander Ebert, Matthias Schmidt
45 * @copyright 2001-2019 WoltLab GmbH
46 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
47 * @package WoltLabSuite\Core\Acp\Package\Plugin
50 class PagePackageInstallationPlugin
extends AbstractXMLPackageInstallationPlugin
implements IGuiPackageInstallationPlugin
{
51 use TXmlGuiPackageInstallationPlugin
;
56 public $className = PageEditor
::class;
62 protected $content = [];
68 protected $pages = [];
73 public $tagName = 'page';
78 protected function handleDelete(array $items) {
80 foreach ($items as $item) {
81 $page = Page
::getPageByIdentifier($item['attributes']['identifier']);
82 if ($page !== null && $page->pageID
&& $page->packageID
== $this->installation
->getPackageID()) $pages[] = $page;
86 $pageAction = new PageAction($pages, 'delete');
87 $pageAction->executeAction();
94 protected function getElement(\DOMXPath
$xpath, array &$elements, \DOMElement
$element) {
95 $nodeValue = $element->nodeValue
;
98 if ($element->tagName
=== 'content') {
99 if (!isset($elements['content'])) $elements['content'] = [];
102 /** @var \DOMElement $child */
103 foreach ($xpath->query('child::*', $element) as $child) {
104 $children[$child->tagName
] = $child->nodeValue
;
107 $elements[$element->tagName
][$element->getAttribute('language')] = $children;
109 else if ($element->tagName
=== 'name') {
110 // <name> can occur multiple times using the `language` attribute
111 if (!isset($elements['name'])) $elements['name'] = [];
113 $elements['name'][$element->getAttribute('language')] = $element->nodeValue
;
116 $elements[$element->tagName
] = $nodeValue;
122 * @throws SystemException
124 protected function prepareImport(array $data) {
125 $pageType = $data['elements']['pageType'];
127 if (!empty($data['elements']['content'])) {
129 foreach ($data['elements']['content'] as $language => $contentData) {
130 if ($pageType != 'system' && !RouteHandler
::isValidCustomUrl($contentData['customURL'])) {
131 throw new SystemException("Invalid custom url for page content '" . $language . "', page identifier '" . $data['attributes']['identifier'] . "'");
134 $content[$language] = [
135 'content' => (!empty($contentData['content'])) ? StringUtil
::trim($contentData['content']) : '',
136 'customURL' => (!empty($contentData['customURL'])) ? StringUtil
::trim($contentData['customURL']) : '',
137 'metaDescription' => (!empty($contentData['metaDescription'])) ? StringUtil
::trim($contentData['metaDescription']) : '',
138 'title' => (!empty($contentData['title'])) ? StringUtil
::trim($contentData['title']) : ''
142 $data['elements']['content'] = $content;
145 // pick the display name by choosing the default language, or 'en' or '' (empty string)
146 $defaultLanguageCode = LanguageFactory
::getInstance()->getDefaultLanguage()->getFixedLanguageCode();
147 if (isset($data['elements']['name'][$defaultLanguageCode])) {
148 // use the default language
149 $name = $data['elements']['name'][$defaultLanguageCode];
151 else if (isset($data['elements']['name']['en'])) {
152 // use the value for English
153 $name = $data['elements']['name']['en'];
155 else if (isset($data['elements']['name'][''])) {
156 // fallback to the display name without/empty language attribute
157 $name = $data['elements']['name'][''];
160 // use whichever value is present, regardless of the language
161 $name = reset($data['elements']['name']);
164 $parentPageID = null;
165 if (!empty($data['elements']['parent'])) {
166 $sql = "SELECT pageID
167 FROM wcf".WCF_N
."_".$this->tableName
."
168 WHERE identifier = ?";
169 $statement = WCF
::getDB()->prepareStatement($sql, 1);
170 $statement->execute([$data['elements']['parent']]);
171 $row = $statement->fetchSingleRow();
172 if ($row === false) {
173 throw new SystemException("Unknown parent page '" . $data['elements']['parent'] . "' for page identifier '" . $data['attributes']['identifier'] . "'");
176 $parentPageID = $row['pageID'];
179 // validate page type
182 $controllerCustomURL = '';
183 $identifier = $data['attributes']['identifier'];
187 if (empty($data['elements']['controller'])) {
188 throw new SystemException("Missing required element 'controller' for 'system'-type page '{$identifier}'");
190 $controller = $data['elements']['controller'];
192 if (!empty($data['elements']['handler'])) {
193 $handler = $data['elements']['handler'];
197 if (!empty($data['elements']['controllerCustomURL'])) {
198 $controllerCustomURL = $data['elements']['controllerCustomURL'];
199 if ($controllerCustomURL && !RouteHandler
::isValidCustomUrl($controllerCustomURL)) {
200 throw new SystemException("Invalid custom url for page identifier '" . $data['attributes']['identifier'] . "'");
209 if (empty($data['elements']['content'])) {
210 throw new SystemException("Missing required 'content' element(s) for page '{$identifier}'");
213 if (count($data['elements']['content']) === 1) {
214 if (!isset($data['elements']['content'][''])) {
215 throw new SystemException("Expected one 'content' element without a 'language' attribute for page '{$identifier}'");
220 if (isset($data['elements']['content'][''])) {
221 throw new SystemException("Cannot mix 'content' elements with and without 'language' attribute for page '{$identifier}'");
228 throw new SystemException("Unknown type '{$pageType}' for page '{$identifier}");
232 // get application package id
233 $applicationPackageID = 1;
234 if ($this->installation
->getPackage()->isApplication
) {
235 $applicationPackageID = $this->installation
->getPackageID();
237 if (!empty($data['elements']['application'])) {
238 $application = PackageCache
::getInstance()->getPackageByIdentifier($data['elements']['application']);
239 if ($application === null ||
!$application->isApplication
) {
240 throw new SystemException("Unknown application '".$data['elements']['application']."' for page '{$identifier}");
242 $applicationPackageID = $application->packageID
;
246 'pageType' => $pageType,
247 'content' => (!empty($data['elements']['content'])) ?
$data['elements']['content'] : [],
248 'controller' => $controller,
249 'handler' => $handler,
250 'controllerCustomURL' => $controllerCustomURL,
251 'identifier' => $identifier,
252 'isMultilingual' => $isMultilingual,
253 'lastUpdateTime' => TIME_NOW
,
255 'originIsSystem' => 1,
256 'parentPageID' => $parentPageID,
257 'applicationPackageID' => $applicationPackageID,
258 'requireObjectID' => (!empty($data['elements']['requireObjectID'])) ?
1 : 0,
259 'options' => isset($data['elements']['options']) ?
$data['elements']['options'] : '',
260 'permissions' => isset($data['elements']['permissions']) ?
$data['elements']['permissions'] : '',
261 'hasFixedParent' => ($pageType == 'system' && !empty($data['elements']['hasFixedParent'])) ?
1 : 0,
262 'cssClassName' => isset($data['elements']['cssClassName']) ?
$data['elements']['cssClassName'] : '',
263 'availableDuringOfflineMode' => (!empty($data['elements']['availableDuringOfflineMode'])) ?
1 : 0,
264 'allowSpidersToIndex' => (!empty($data['elements']['allowSpidersToIndex'])) ?
1 : 0,
265 'excludeFromLandingPage' => (!empty($data['elements']['excludeFromLandingPage'])) ?
1 : 0
272 protected function findExistingItem(array $data) {
274 FROM wcf".WCF_N
."_".$this->tableName
."
279 $this->installation
->getPackageID()
284 'parameters' => $parameters
291 protected function import(array $row, array $data) {
293 $content = $data['content'];
294 unset($data['content']);
296 /** @var Page $page */
298 // allow update of `controller`, `handler` and `excludeFromLandingPage`
299 // only, prevents user modifications form being overwritten
300 if (!empty($data['controller'])) {
301 $allowSpidersToIndex = $row['allowSpidersToIndex'] ??
0;
302 if ($allowSpidersToIndex == 2) {
303 // The value `2` resolves to be true-ish, eventually resulting in the same behavior
304 // when setting it to `1`. This value is special to the 3.0 -> 3.1 upgrade, because
305 // it force-enables the visibility, while also being some sort of indicator for non-
306 // user-modified values. The page edit form will set it to either `1` or `0`, there-
307 // fore `2` means that we can safely update the value w/o breaking the user's choice.
308 $allowSpidersToIndex = $data['allowSpidersToIndex'];
311 $page = parent
::import($row, [
312 'controller' => $data['controller'],
313 'handler' => $data['handler'] ??
'',
314 'options' => $data['options'] ??
'',
315 'permissions' => $data['permissions'] ??
'',
316 'excludeFromLandingPage' => $data['excludeFromLandingPage'] ??
0,
317 'allowSpidersToIndex' => $allowSpidersToIndex,
318 'requireObjectID' => $data['requireObjectID'],
322 $baseClass = call_user_func([$this->className
, 'getBaseClass']);
323 $page = new $baseClass(null, $row);
328 $page = parent
::import($row, $data);
331 // store content for later import
332 $this->pages
[$page->pageID
] = $page;
333 $this->content
[$page->pageID
] = $content;
341 protected function postImport() {
342 if (!empty($this->content
)) {
343 $sql = "SELECT COUNT(*) AS count
344 FROM wcf".WCF_N
."_page_content
346 AND languageID IS NULL";
347 $statement = WCF
::getDB()->prepareStatement($sql);
349 $sql = "INSERT IGNORE INTO wcf".WCF_N
."_page_content
350 (pageID, languageID, title, content, metaDescription, customURL)
351 VALUES (?, ?, ?, ?, ?, ?)";
352 $insertStatement = WCF
::getDB()->prepareStatement($sql);
354 WCF
::getDB()->beginTransaction();
355 foreach ($this->content
as $pageID => $contentData) {
356 foreach ($contentData as $languageCode => $content) {
358 if ($languageCode != '') {
359 $language = LanguageFactory
::getInstance()->getLanguageByCode($languageCode);
360 if ($language === null) continue;
362 $languageID = $language->languageID
;
365 if ($languageID === null) {
366 $statement->execute([$pageID]);
367 if ($statement->fetchColumn()) continue;
370 $insertStatement->execute([
375 $content['metaDescription'],
376 $content['customURL'],
379 // generate template if page's type is 'tpl'
380 $page = new Page($pageID);
381 if ($page->pageType
== 'tpl') {
382 (new PageEditor($page))->updateTemplate($languageID, $content['content']);
386 WCF
::getDB()->commitTransaction();
388 // create search index tables
389 SearchIndexManager
::getInstance()->createSearchIndices();
391 // update search index
392 foreach ($this->pages
as $pageID => $page) {
393 if ($page->pageType
== 'text' ||
$page->pageType
== 'html') {
394 foreach ($page->getPageContents() as $languageID => $pageContent) {
395 SearchIndexManager
::getInstance()->set(
396 'com.woltlab.wcf.page',
397 $pageContent->pageContentID
,
398 $pageContent->content
,
415 public static function getSyncDependencies() {
423 protected function addFormFields(IFormDocument
$form) {
424 $tabContainter = TabMenuFormContainer
::create('tabMenu');
425 $form->appendChild($tabContainter);
427 $dataTab = TabFormContainer
::create('dataTab')
428 ->label('wcf.global.form.data');
429 $tabContainter->appendChild($dataTab);
430 $dataContainer = FormContainer
::create('dataTabData');
431 $dataTab->appendChild($dataContainer);
433 $contentTab = TabFormContainer
::create('contentTab')
434 ->label('wcf.acp.pip.page.content');
435 $tabContainter->appendChild($contentTab);
436 $contentContainer = FormContainer
::create('contentTabContent');
437 $contentTab->appendChild($contentContainer);
439 $dataContainer->appendChildren([
440 TextFormField
::create('identifier')
441 ->label('wcf.acp.pip.page.identifier')
442 ->description('wcf.acp.pip.page.identifier.description')
444 ->addValidator(FormFieldValidatorUtil
::getDotSeparatedStringValidator(
445 'wcf.acp.pip.page.identifier',
448 ->addValidator(new FormFieldValidator('uniqueness', function(TextFormField
$formField) {
450 $formField->getDocument()->getFormMode() === IFormDocument
::FORM_MODE_CREATE ||
451 $this->editedEntry
->getAttribute('identifier') !== $formField->getValue()
453 $pageList = new PageList();
454 $pageList->getConditionBuilder()->add('identifier = ?', [$formField->getValue()]);
456 if ($pageList->countObjects() > 0) {
457 $formField->addValidationError(
458 new FormFieldValidationError(
460 'wcf.acp.pip.page.identifier.error.notUnique'
467 RadioButtonFormField
::create('pageType')
468 ->label('wcf.acp.pip.page.pageType')
469 ->description('wcf.acp.pip.page.pageType.description')
470 ->options(array_combine(Page
::$availablePageTypes, Page
::$availablePageTypes))
471 ->addClass('floated'),
473 TextFormField
::create('name')
474 ->label('wcf.acp.pip.page.name')
475 ->description('wcf.acp.pip.page.name.description')
479 ->languageItemPattern('__NONE__'),
481 ClassNameFormField
::create('controller')
482 ->label('wcf.acp.pip.page.controller')
483 ->implementedInterface(IPage
::class)
486 ClassNameFormField
::create('handler')
487 ->label('wcf.acp.pip.page.handler')
488 ->implementedInterface(IMenuPageHandler
::class),
490 BooleanFormField
::create('requireObjectID')
491 ->label('wcf.acp.pip.page.requireObjectID')
492 ->description('wcf.acp.pip.page.requireObjectID.description'),
494 SingleSelectionFormField
::create('parent')
495 ->label('wcf.acp.pip.page.parent')
498 ->options(function() {
499 $pageNodeList = (new PageNodeTree())->getNodeList();
503 'label' => 'wcf.global.noSelection',
507 $packageIDs = array_merge(
508 [$this->installation
->getPackage()->packageID
],
509 array_keys($this->installation
->getPackage()->getAllRequiredPackages())
512 /** @var PageNode $pageNode */
513 foreach ($pageNodeList as $pageNode) {
514 if (in_array($pageNode->packageID
, $packageIDs)) {
516 'depth' => $pageNode->getDepth() - 1,
517 'label' => $pageNode->name
,
518 'value' => $pageNode->identifier
523 return $nestedOptions;
525 ->addValidator(new FormFieldValidator('selfParent', function(SingleSelectionFormField
$formField) {
526 /** @var TextFormField $identifier */
527 $identifier = $formField->getDocument()->getNodeById('identifier');
529 if ($identifier->getSaveValue() === $formField->getValue()) {
530 $formField->addValidationError(
531 new FormFieldValidationError(
533 'wcf.acp.pip.page.parent.error.selfParent'
539 BooleanFormField
::create('hasFixedParent')
540 ->label('wcf.acp.pip.page.hasFixedParent')
541 ->description('wcf.acp.pip.page.hasFixedParent.description'),
543 OptionFormField
::create()
544 ->description('wcf.acp.pip.page.options.description')
545 ->packageIDs(array_merge(
546 [$this->installation
->getPackage()->packageID
],
547 array_keys($this->installation
->getPackage()->getAllRequiredPackages())
550 UserGroupOptionFormField
::create()
551 ->description('wcf.acp.pip.page.permissions.description')
552 ->packageIDs(array_merge(
553 [$this->installation
->getPackage()->packageID
],
554 array_keys($this->installation
->getPackage()->getAllRequiredPackages())
557 ItemListFormField
::create('cssClassName')
558 ->label('wcf.acp.pip.page.cssClassName')
559 ->description('wcf.acp.pip.page.cssClassName.description'),
561 BooleanFormField
::create('allowSpidersToIndex')
562 ->label('wcf.acp.pip.page.allowSpidersToIndex'),
564 BooleanFormField
::create('excludeFromLandingPage')
565 ->label('wcf.acp.pip.page.excludeFromLandingPage'),
567 BooleanFormField
::create('availableDuringOfflineMode')
568 ->label('wcf.acp.pip.page.availableDuringOfflineMode')
571 $contentContainer->appendChildren([
572 TitleFormField
::create('contentTitle')
573 ->objectProperty('title')
574 ->label('wcf.acp.pip.page.contentTitle')
577 ->languageItemPattern('__NONE__'),
579 MultilineTextFormField
::create('contentContent')
580 ->objectProperty('content')
581 ->label('wcf.acp.pip.page.contentContent')
584 ->languageItemPattern('__NONE__'),
586 TextFormField
::create('contentCustomURL')
587 ->objectProperty('customURL')
588 ->label('wcf.acp.pip.page.contentCustomURL')
591 ->languageItemPattern('__NONE__'),
593 TextFormField
::create('contentMetaDescription')
594 ->objectProperty('metaDescription')
595 ->label('wcf.acp.pip.page.contentMetaDescription')
598 ->languageItemPattern('__NONE__'),
603 /** @var RadioButtonFormField $pageType */
604 $pageType = $form->getNodeById('pageType');
605 foreach (['controller', 'handler', 'requireObjectID'] as $systemElement) {
606 $form->getNodeById($systemElement)->addDependency(
607 ValueFormFieldDependency
::create('pageType')
613 foreach (['contentContent', 'contentCustomURL', 'contentMetaDescription'] as $nonSystemElement) {
614 $form->getNodeById($nonSystemElement)->addDependency(
615 ValueFormFieldDependency
::create('pageType')
627 protected function fetchElementData(\DOMElement
$element, $saveData) {
629 'identifier' => $element->getAttribute('identifier'),
630 'originIsSystem' => 1,
631 'packageID' => $this->installation
->getPackageID(),
632 'pageType' => $element->getElementsByTagName('pageType')->item(0)->nodeValue
,
637 'metaDescription' => [],
640 /** @var \DOMElement $name */
641 foreach ($element->getElementsByTagName('name') as $name) {
642 $data['name'][LanguageFactory
::getInstance()->getLanguageByCode($name->getAttribute('language'))->languageID
] = $name->nodeValue
;
645 $optionalElements = [
646 'controller', 'handler', 'hasFixedParent',
647 'parent', 'options', 'permissions', 'cssClassName', 'allowSpidersToIndex',
648 'excludeFromLandingPage', 'availableDuringOfflineMode', 'requireObjectID'
651 $zeroDefaultOptions = [
653 'allowSpidersToIndex',
654 'excludeFromLandingPage',
655 'availableDuringOfflineMode',
659 foreach ($optionalElements as $optionalElementName) {
660 $optionalElement = $element->getElementsByTagName($optionalElementName)->item(0);
661 if ($optionalElement !== null) {
662 $data[$optionalElementName] = $optionalElement->nodeValue
;
664 else if ($saveData) {
665 if (in_array($optionalElementName, $zeroDefaultOptions)) {
666 $data[$optionalElementName] = 0;
669 $data[$optionalElementName] = '';
674 $readData = function($languageID, \DOMElement
$content) use (&$data, $saveData) {
675 foreach (['title', 'content', 'customURL', 'metaDescription'] as $contentElementName) {
676 $contentElement = $content->getElementsByTagName($contentElementName)->item(0);
677 if (!isset($data[$contentElementName])) {
678 $data[$contentElementName] = [];
681 if ($contentElement) {
682 $data[$contentElementName][$languageID] = $contentElement->nodeValue
;
684 else if ($saveData) {
685 $data[$contentElementName][$languageID] = '';
690 /** @var \DOMElement $content */
691 foreach ($element->getElementsByTagName('content') as $content) {
692 $languageCode = $content->getAttribute('language');
693 if ($languageCode === '') {
694 foreach (LanguageFactory
::getInstance()->getLanguages() as $language) {
695 $readData($language->languageID
, $content);
700 LanguageFactory
::getInstance()->getLanguageByCode($languageCode)->languageID
,
707 if ($this->editedEntry
!== null) {
708 unset($data['name']);
712 foreach ($data['name'] as $languageID => $title) {
713 $titles[LanguageFactory
::getInstance()->getLanguage($languageID)->languageCode
] = $title;
716 if (isset($data['name'][LanguageFactory
::getInstance()->getDefaultLanguage()->languageID
])) {
717 // use the default language
718 $data['name'] = $data['name'][LanguageFactory
::getInstance()->getDefaultLanguage()->languageID
];
721 $english = LanguageFactory
::getInstance()->getLanguageByCode('en');
722 if ($english !== null && isset($data['name'][$english->languageID
])) {
723 $data['name'] = $data['name'][$english->languageID
];
726 $data['name'] = reset($data['name']);
733 foreach (['title', 'content', 'customURL', 'metaDescription'] as $contentProperty) {
734 if (!empty($data[$contentProperty])) {
735 foreach ($data[$contentProperty] as $languageID => $value) {
736 $languageCode = LanguageFactory
::getInstance()->getLanguage($languageID)->languageCode
;
738 if (!isset($content[$languageCode])) {
739 $content[$languageCode] = [];
742 $content[$languageCode][$contentProperty] = $value;
746 unset($data[$contentProperty]);
749 foreach ($content as $languageCode => $values) {
750 foreach (['title', 'content', 'customURL', 'metaDescription'] as $contentProperty) {
751 if (!isset($values[$contentProperty])) {
752 $content[$languageCode][$contentProperty] = '';
757 $data['content'] = $content;
759 if (isset($data['parent'])) {
760 $parent = $data['parent'];
761 unset($data['parent']);
763 if (!empty($parent)) {
764 $data['parentPageID'] = Page
::getPageByIdentifier($parent)->pageID
;
776 public function getElementIdentifier(\DOMElement
$element) {
777 return $element->getAttribute('identifier');
784 protected function setEntryListKeys(IDevtoolsPipEntryList
$entryList) {
785 $entryList->setKeys([
786 'identifier' => 'wcf.acp.pip.page.identifier',
787 'pageType' => 'wcf.acp.pip.page.pageType'
795 protected function prepareXmlElement(\DOMDocument
$document, IFormDocument
$form) {
796 $formData = $form->getData();
797 $data = $formData['data'];
799 $page = $document->createElement($this->tagName
);
800 $page->setAttribute('identifier', $data['identifier']);
802 $page->appendChild($document->createElement('pageType', $data['pageType']));
804 $this->appendElementChildren(
806 ['controller' => '',],
810 foreach ($formData['name_i18n'] as $languageID => $name) {
811 $name = $document->createElement('name', $this->getAutoCdataValue($name));
812 $name->setAttribute('language', LanguageFactory
::getInstance()->getLanguage($languageID)->languageCode
);
814 $page->appendChild($name);
817 $this->appendElementChildren(
821 'hasFixedParent' => 0,
825 'cssClassName' => '',
826 'allowSpidersToIndex' => 0,
827 'excludeFromLandingPage' => 0,
828 'availableDuringOfflineMode' => 0,
829 'requireObjectID' => 0
834 $languages = LanguageFactory
::getInstance()->getLanguages();
836 // sort languages by language code but keep English first
837 uasort($languages, function(Language
$language1, Language
$language2) {
838 if ($language1->languageCode
=== 'en') {
841 else if ($language2->languageCode
=== 'en') {
845 return $language1->languageCode
<=> $language2->languageCode
;
848 foreach ($languages as $language) {
851 foreach (['title', 'content', 'customURL', 'metaDescription'] as $property) {
852 if (!empty($formData[$property . '_i18n'][$language->languageID
])) {
853 if ($content === null) {
854 $content = $document->createElement('content');
855 $content->setAttribute('language', $language->languageCode
);
857 $page->appendChild($content);
860 if ($property === 'content') {
861 $contentContent = $document->createElement('content');
862 $contentContent->appendChild(
863 $document->createCDATASection(
864 StringUtil
::escapeCDATA(StringUtil
::unifyNewlines(
865 $formData[$property . '_i18n'][$language->languageID
]
870 $content->appendChild($contentContent);
873 $content->appendChild(
874 $document->createElement(
876 $formData[$property . '_i18n'][$language->languageID
]