diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 87458e8..75f9bb2 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,7 +1,6 @@ in(__DIR__.'/files/') - ->notPath('lib/system/api'); + ->in(__DIR__.'/files/'); return (new PhpCsFixer\Config()) ->setRiskyAllowed(true) @@ -63,7 +62,7 @@ 'return_type_declaration' => true, 'static_lambda' => true, - 'fully_qualified_strict_types' => true, + 'fully_qualified_strict_types' => ['leading_backslash_in_global_namespace' => true], 'no_leading_import_slash' => true, 'no_unused_imports' => true, 'ordered_imports' => true, @@ -116,14 +115,5 @@ 'method_chaining_indentation' => true, 'no_extra_blank_lines' => ['tokens' => ['case', 'continue', 'curly_brace_block', 'default', 'extra', 'parenthesis_brace_block', 'square_brace_block', 'switch', 'throw', 'use']], 'no_spaces_around_offset' => true, - - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => false, - 'import_functions' => false, - ], - 'ordered_imports' => [ - 'imports_order' => ['class', 'function', 'const'], - ], ]) ->setFinder($finder); diff --git a/files/lib/system/wsdb/database/preset/LinkPreset.class.php b/files/lib/system/wsdb/database/preset/LinkPreset.class.php index 977c343..1b4aa60 100644 --- a/files/lib/system/wsdb/database/preset/LinkPreset.class.php +++ b/files/lib/system/wsdb/database/preset/LinkPreset.class.php @@ -69,7 +69,7 @@ public function getListingTemplateName(): string #[Override] public function getShowTeaserInListing(): bool { - return true; + return false; } #[Override] diff --git a/files/lib/system/wsdb/exporter/LinkDatabaseExporter.class.php b/files/lib/system/wsdb/exporter/LinkDatabaseExporter.class.php new file mode 100644 index 0000000..cd761bf --- /dev/null +++ b/files/lib/system/wsdb/exporter/LinkDatabaseExporter.class.php @@ -0,0 +1,801 @@ + 'Database', + 'dev.hanashi.wsdb.links.permission' => 'Permissions', + 'dev.hanashi.wsdb.links.category' => 'Categories', + 'dev.hanashi.wsdb.links.category.acl' => 'CategoryACLs', + 'dev.hanashi.wsdb.links.option' => 'Options', + 'dev.hanashi.wsdb.links.links' => 'Links', + 'dev.hanashi.wsdb.links.attachments' => 'Attachments', + 'dev.hanashi.wsdb.links.option.values' => 'OptionValues', + 'dev.hanashi.wsdb.links.comment' => 'Comments', + 'dev.hanashi.wsdb.links.comment.response' => 'CommentResponses', + 'dev.hanashi.wsdb.links.reaction' => 'Reactions', + ]; + + #[\Override] + public function init() + { + $this->database = WCF::getDB(); + $this->fileSystemPath = WCF_DIR; + } + + #[\Override] + public function getDefaultDatabasePrefix() + { + return 'wcf' . WCF_N . '_'; + } + + #[\Override] + public function validateFileAccess() + { + // no file access needable + return true; + } + + #[\Override] + public function validateSelectedData(array $selectedData) + { + $package = PackageCache::getInstance()->getPackageByIdentifier('de.pehbeh.links'); + if ($package === null) { + throw new UserInputException('selectedData', 'linkPackageNotInstalled'); + } + + return parent::validateSelectedData($selectedData); + } + + #[\Override] + public function getQueue() + { + $queue = []; + + if (\in_array('dev.hanashi.wsdb.links', $this->selectedData)) { + $queue[] = 'dev.hanashi.wsdb.links'; + if (\in_array('dev.hanashi.wsdb.links.permission', $this->selectedData)) { + $queue[] = 'dev.hanashi.wsdb.links.permission'; + } + + if (\in_array('dev.hanashi.wsdb.links.category', $this->selectedData)) { + $queue[] = 'dev.hanashi.wsdb.links.category'; + if (\in_array('dev.hanashi.wsdb.links.category.acl', $this->selectedData)) { + $queue[] = 'dev.hanashi.wsdb.links.category.acl'; + } + } + if (\in_array('dev.hanashi.wsdb.links.option', $this->selectedData)) { + $queue[] = 'dev.hanashi.wsdb.links.option'; + } + if (\in_array('dev.hanashi.wsdb.links.links', $this->selectedData)) { + $queue[] = 'dev.hanashi.wsdb.links.links'; + if (\in_array('dev.hanashi.wsdb.links.attachments', $this->selectedData)) { + $queue[] = 'dev.hanashi.wsdb.links.attachments'; + } + if ( + \in_array('dev.hanashi.wsdb.links.option', $this->selectedData) + && \in_array('dev.hanashi.wsdb.links.option.values', $this->selectedData) + ) { + $queue[] = 'dev.hanashi.wsdb.links.option.values'; + } + if (\in_array('dev.hanashi.wsdb.links.comment', $this->selectedData)) { + $queue[] = 'dev.hanashi.wsdb.links.comment'; + if (\in_array('dev.hanashi.wsdb.links.comment.response', $this->selectedData)) { + $queue[] = 'dev.hanashi.wsdb.links.comment.response'; + } + } + if (\in_array('dev.hanashi.wsdb.links.reaction', $this->selectedData)) { + $queue[] = 'dev.hanashi.wsdb.links.reaction'; + } + } + } + + return $queue; + } + + #[\Override] + public function getSupportedData(): array + { + return [ + 'dev.hanashi.wsdb.links' => [ + 'dev.hanashi.wsdb.links.permission', + 'dev.hanashi.wsdb.links.category', + 'dev.hanashi.wsdb.links.category.acl', + 'dev.hanashi.wsdb.links.option', + 'dev.hanashi.wsdb.links.links', + 'dev.hanashi.wsdb.links.attachments', + 'dev.hanashi.wsdb.links.option.values', + 'dev.hanashi.wsdb.links.comment', + 'dev.hanashi.wsdb.links.comment.response', + 'dev.hanashi.wsdb.links.reaction', + ], + ]; + } + + public function countDatabase(): int + { + return 1; + } + + public function exportDatabase(): void + { + ImportHandler::getInstance() + ->getImporter('dev.hanashi.wsdb.links') + ->import(1, []); + } + + public function countPermissions(): int + { + $sql = "SELECT COUNT(*) + FROM wcf1_user_group"; + + $statement = $this->database->prepare($sql); + $statement->execute(); + + return $statement->fetchSingleColumn(); + } + + public function exportPermissions(int $offset, int $limit): void + { + $sql = "SELECT * + FROM wcf1_user_group + ORDER BY groupID"; + + $statement = $this->database->prepare($sql, $limit, $offset); + $statement->execute(); + + $groupIDs = []; + while ($row = $statement->fetchArray()) { + ImportHandler::getInstance()->saveNewID('com.woltlab.wcf.user.group', $row['groupID'], $row['groupID']); + $groupIDs[] = $row['groupID']; + } + if ($groupIDs === []) { + return; + } + + $permissionMap = [ + // users + 'user.links.canViewLinkEntry' => ['canViewRecord'], + 'user.links.canAddLinkEntry' => ['canAddRecord'], + 'user.links.canAddLinkEntryWithoutModeration' => ['canAddRecordWithoutModeration'], + 'user.links.canUploadAttachment' => ['canUploadAttachment'], + 'user.links.canDownloadAttachment' => ['canDownloadAttachment'], + 'user.links.canViewAttachmentPreview' => ['canViewAttachmentPreview'], + 'user.links.canEditLinkEntry' => ['canEditOwnRecord'], + 'user.links.canDeleteLinkEntry' => ['canDeleteOwnRecord'], + 'user.links.canAddComment' => [ + 'canAddComment', + 'canAddReview', + ], + 'user.links.canAddCommentWithoutModeration' => ['canAddCommentWithoutModeration'], + 'user.links.canEditComment' => [ + 'canEditOwnComment', + 'canEditOwnReview', + ], + 'user.links.canDeleteComment' => [ + 'canDeleteOwnComment', + 'canDeleteOwnReview', + ], + // mod + 'mod.links.canModerateLinkEntry' => [ + 'canEditRecord', + 'canViewDeletedRecord', + 'canRestoreRecord', + 'canDeleteRecordCompletely', + 'canEnableRecord', + ], + 'mod.links.canDeleteLinkEntry' => ['canDeleteRecord'], + ]; + + $permissions = \array_keys($permissionMap); + $conditionBuilder = new PreparedStatementConditionBuilder(); + $conditionBuilder->add('option_value.groupID IN (?)', [$groupIDs]); + $conditionBuilder->add('group_option.optionName IN (?)', [$permissions]); + + $sql = "SELECT group_option.optionName, + option_value.groupID, + option_value.optionValue + FROM wcf1_user_group_option group_option + INNER JOIN wcf1_user_group_option_value option_value + ON option_value.optionID = group_option.optionID + " . $conditionBuilder; + $statement = $this->database->prepare($sql); + $statement->execute($conditionBuilder->getParameters()); + + while ($row = $statement->fetchArray()) { + $acls = $permissionMap[$row['optionName']] ?? []; + foreach ($acls as $acl) { + ImportHandler::getInstance() + ->getImporter('dev.hanashi.wsdb.links.permission') + ->import( + 0, + [ + 'objectID' => 1, + 'optionValue' => $row['optionValue'], + 'groupID' => $row['groupID'], + ], + ['optionName' => $acl] + ); + } + } + } + + public function countCategories(): int + { + $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName( + 'com.woltlab.wcf.category', + 'de.pehbeh.links.category' + ); + if ($objectType === null) { + return 0; + } + + $sql = "SELECT COUNT(*) + FROM wcf1_category + WHERE objectTypeID = ?"; + + $statement = $this->database->prepare($sql); + $statement->execute([$objectType->objectTypeID]); + + return $statement->fetchSingleColumn(); + } + + public function exportCategories(int $offset, int $limit): void + { + $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName( + 'com.woltlab.wcf.category', + 'de.pehbeh.links.category' + ); + if ($objectType === null) { + return; + } + + $sql = "SELECT * + FROM wcf1_category + WHERE objectTypeID = ? + ORDER BY parentCategoryID, categoryID"; + $statement = $this->database->prepare($sql, $limit, $offset); + $statement->execute([$objectType->objectTypeID]); + $categories = $i18nValues = []; + while ($row = $statement->fetchArray()) { + $row['description'] = $row['description'] ?? ''; + + $categories[$row['categoryID']] = [ + 'title' => $row['title'], + 'description' => $row['description'], + 'parentCategoryID' => $row['parentCategoryID'], + 'showOrder' => $row['showOrder'], + 'time' => $row['time'], + 'isDisabled' => $row['isDisabled'], + ]; + + if (\str_starts_with($row['title'], 'wcf.category')) { + $i18nValues[] = $row['title']; + } + if (\str_starts_with($row['description'], 'wcf.category')) { + $i18nValues[] = $row['description']; + } + if ($row['additionalData'] !== null && @\unserialize($row['additionalData']) !== false) { + $oldAdditionalData = \unserialize($row['additionalData']); + $newAdditionalData = []; + if (isset($oldAdditionalData['linkEntryCategoryIcon'])) { + $iconData = \explode(';', $oldAdditionalData['linkEntryCategoryIcon']); + if (\count($iconData) === 2) { + $newAdditionalData['icon'] = $oldAdditionalData['linkEntryCategoryIcon']; + } elseif (!empty(\trim($iconData[0]))) { + $newAdditionalData['icon'] = $iconData[0] . ';' . 'false'; + } + } + $categories[$row['categoryID']]['additionalData'] = \serialize($newAdditionalData); + } + } + + $i18nValues = $this->getI18nValues($i18nValues); + + foreach ($categories as $categoryID => $categoryData) { + $i18nData = []; + if (isset($i18nValues[$categoryData['title']])) { + $i18nData['title'] = $i18nValues[$categoryData['title']]; + } + if (isset($i18nValues[$categoryData['description']])) { + $i18nData['description'] = $i18nValues[$categoryData['description']]; + } + + ImportHandler::getInstance() + ->getImporter('dev.hanashi.wsdb.links.category') + ->import( + $categoryID, + $categoryData, + ['i18n' => $i18nData] + ); + } + } + + public function countCategoryACLs(): int + { + $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName( + 'com.woltlab.wcf.acl', + 'de.pehbeh.links.category' + ); + if ($objectType === null) { + return 0; + } + + $sql = "SELECT ( + SELECT COUNT(*) + FROM wcf1_acl_option_to_group acl_option_to_group + INNER JOIN wcf1_acl_option acl_option + ON acl_option.optionID = acl_option_to_group.optionID + WHERE acl_option.objectTypeID = ? + ) + ( + SELECT COUNT(*) + FROM wcf1_acl_option_to_user acl_option_to_user + INNER JOIN wcf1_acl_option acl_option + ON acl_option.optionID = acl_option_to_user.optionID + WHERE acl_option.objectTypeID = ? + ) AS count"; + $statement = $this->database->prepare($sql); + $statement->execute([$objectType->objectTypeID, $objectType->objectTypeID]); + + return $statement->fetchSingleColumn(); + } + + public function exportCategoryACLs(int $offset, int $limit): void + { + $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName( + 'com.woltlab.wcf.acl', + 'de.pehbeh.links.category' + ); + if ($objectType === null) { + return; + } + + $aclMap = [ + 'canViewCategory' => [ + 'canViewCategory', + 'canViewRecord', + ], + 'canAddLinkEntry' => [ + 'canAddRecord', + ], + ]; + + $sql = "( + SELECT acl_option.optionName, acl_option.optionID, + option_to_group.objectID, option_to_group.optionValue, 0 AS userID, option_to_group.groupID + FROM wcf1_acl_option_to_group option_to_group, + wcf1_acl_option acl_option + WHERE acl_option.optionID = option_to_group.optionID + AND acl_option.objectTypeID = ? + ) + UNION + ( + SELECT acl_option.optionName, acl_option.optionID, + option_to_user.objectID, option_to_user.optionValue, option_to_user.userID, 0 AS groupID + FROM wcf1_acl_option_to_user option_to_user, + wcf1_acl_option acl_option + WHERE acl_option.optionID = option_to_user.optionID + AND acl_option.objectTypeID = ? + ) + ORDER BY optionID, objectID, userID, groupID"; + $statement = $this->database->prepare($sql, $limit, $offset); + $statement->execute([$objectType->objectTypeID, $objectType->objectTypeID]); + while ($row = $statement->fetchArray()) { + $acls = $aclMap[$row['optionName']] ?? []; + foreach ($acls as $acl) { + $data = [ + 'objectID' => $row['objectID'], + 'optionValue' => $row['optionValue'], + ]; + if ($row['userID']) { + $data['userID'] = $row['userID']; + } + if ($row['groupID']) { + $data['groupID'] = $row['groupID']; + } + + ImportHandler::getInstance() + ->getImporter('dev.hanashi.wsdb.links.category.acl') + ->import( + 0, + $data, + ['optionName' => $acl] + ); + } + } + } + + public function countOptions(): int + { + $sql = "SELECT COUNT(*) + FROM wcf1_links_option"; + + $statement = $this->database->prepare($sql); + $statement->execute(); + + return $statement->fetchSingleColumn(); + } + + public function exportOptions(int $offset, int $limit): void + { + $sql = "SELECT optionID, + categoryID + FROM wcf1_links_option_to_category"; + $statement = $this->database->prepare($sql); + $statement->execute(); + $categories = $statement->fetchMap('optionID', 'categoryID', false); + + $sql = "SELECT * + FROM wcf1_links_option + ORDER BY optionID"; + $statement = $this->database->prepare($sql, $limit, $offset); + $statement->execute(); + + while ($row = $statement->fetchArray()) { + ImportHandler::getInstance() + ->getImporter('dev.hanashi.wsdb.links.option') + ->import( + $row['optionID'], + $row, + [ + 'categories' => $categories[$row['optionID']] ?? [], + ] + ); + } + } + + public function countLinks(): int + { + $sql = "SELECT COUNT(*) + FROM wcf1_links"; + + $statement = $this->database->prepare($sql); + $statement->execute(); + + return $statement->fetchSingleColumn(); + } + + public function exportLinks(int $offset, int $limit): void + { + $sql = "SELECT * + FROM wcf1_links + ORDER BY linkID"; + $statement = $this->database->prepare($sql, $limit, $offset); + $statement->execute(); + + $tags = $this->getLinkTags(); + + while ($row = $statement->fetchArray()) { + ImportHandler::getInstance() + ->getImporter('dev.hanashi.wsdb.links.links') + ->import( + $row['linkID'], + $row, + [ + 'tags' => $tags[$row['linkID']] ?? [], + ] + ); + } + } + + public function countAttachments(): int + { + $sql = "SELECT COUNT(*) + FROM wcf1_links + WHERE attachments <> ?"; + + $statement = $this->database->prepare($sql); + $statement->execute([0]); + + return $statement->fetchSingleColumn(); + } + + public function exportAttachments(int $offset, int $limit): void + { + $sql = "SELECT linkID + FROM wcf1_links + WHERE attachments <> ? + ORDER BY linkID"; + $statement = $this->database->prepare($sql, $limit, $offset); + $statement->execute([0]); + + while ($row = $statement->fetchArray()) { + ImportHandler::getInstance() + ->getImporter('dev.hanashi.wsdb.links.attachments') + ->import( + 0, + $row + ); + } + } + + public function countOptionValues(): int + { + return $this->countLinks(); + } + + public function exportOptionValues(int $offset, int $limit): void + { + $sql = "SELECT linkID + FROM wcf1_links + ORDER BY linkID"; + $statement = $this->database->prepare($sql, $limit, $offset); + $statement->execute(); + $linkIDs = $statement->fetchAll(\PDO::FETCH_COLUMN); + if ($linkIDs === []) { + return; + } + + $conditionBuilder = new PreparedStatementConditionBuilder(); + $conditionBuilder->add('links_option_value.linkID IN (?)', [$linkIDs]); + + $sql = "SELECT links_option_value.*, + links_option.optionType + FROM wcf1_links_option_value links_option_value + INNER JOIN wcf1_links_option links_option + ON links_option.optionID = links_option_value.optionID + " . $conditionBuilder . " + ORDER BY links_option_value.linkID"; + $statement = $this->database->prepare($sql); + $statement->execute($conditionBuilder->getParameters()); + + $optionValues = []; + while ($row = $statement->fetchArray()) { + $optionValues[$row['linkID']][] = $row; + } + if ($optionValues === []) { + return; + } + + foreach ($optionValues as $linkID => $data) { + ImportHandler::getInstance() + ->getImporter('dev.hanashi.wsdb.links.option.values') + ->import( + 0, + $data, + [ + 'linkID' => $linkID, + ] + ); + } + } + + public function countComments(): int + { + $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName( + 'com.woltlab.wcf.comment.commentableContent', + 'de.pehbeh.links.linkEntryComment' + ); + if ($objectType === null) { + return 0; + } + + $sql = "SELECT COUNT(*) + FROM wcf1_comment + WHERE objectTypeID = ?"; + $statement = $this->database->prepare($sql); + $statement->execute([$objectType->objectTypeID]); + + return $statement->fetchSingleColumn(); + } + + public function exportComments(int $offset, int $limit): void + { + $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName( + 'com.woltlab.wcf.comment.commentableContent', + 'de.pehbeh.links.linkEntryComment' + ); + if ($objectType === null) { + return; + } + + $sql = "SELECT * + FROM wcf1_comment + WHERE objectTypeID = ? + ORDER BY commentID"; + $statement = $this->database->prepare($sql, $limit, $offset); + $statement->execute([$objectType->objectTypeID]); + while ($row = $statement->fetchArray()) { + $data = [ + 'objectID' => $row['objectID'], + 'userID' => $row['userID'], + 'username' => $row['username'], + 'message' => $row['message'], + 'time' => $row['time'], + 'enableHtml' => (isset($row['enableHtml'])) ? $row['enableHtml'] : 0, + 'isDisabled' => (isset($row['isDisabled'])) ? $row['isDisabled'] : 0, + ]; + + ImportHandler::getInstance() + ->getImporter('dev.hanashi.wsdb.links.comment') + ->import($row['commentID'], $data); + } + } + + public function countCommentResponses(): int + { + $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName( + 'com.woltlab.wcf.comment.commentableContent', + 'de.pehbeh.links.linkEntryComment' + ); + if ($objectType === null) { + return 0; + } + + $sql = "SELECT COUNT(*) AS count + FROM wcf1_comment_response + WHERE commentID IN ( + SELECT commentID + FROM wcf1_comment + WHERE objectTypeID = ? + )"; + $statement = $this->database->prepare($sql); + $statement->execute([$objectType->objectTypeID]); + + return $statement->fetchSingleColumn(); + } + + public function exportCommentResponses(int $offset, int $limit): void + { + $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName( + 'com.woltlab.wcf.comment.commentableContent', + 'de.pehbeh.links.linkEntryComment' + ); + if ($objectType === null) { + return; + } + + $sql = "SELECT * + FROM wcf1_comment_response + WHERE commentID IN ( + SELECT commentID + FROM wcf1_comment + WHERE objectTypeID = ? + ) + ORDER BY responseID"; + $statement = $this->database->prepare($sql, $limit, $offset); + $statement->execute([$objectType->objectTypeID]); + while ($row = $statement->fetchArray()) { + $data = [ + 'commentID' => $row['commentID'], + 'time' => $row['time'], + 'userID' => $row['userID'], + 'username' => $row['username'], + 'message' => $row['message'], + 'enableHtml' => (isset($row['enableHtml'])) ? $row['enableHtml'] : 0, + 'isDisabled' => (isset($row['isDisabled'])) ? $row['isDisabled'] : 0, + ]; + + ImportHandler::getInstance() + ->getImporter('dev.hanashi.wsdb.links.comment.response') + ->import($row['responseID'], $data); + } + } + + public function countReactions(): int + { + $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName( + 'com.woltlab.wcf.like.likeableObject', + 'de.pehbeh.links.likeableLinkEntry' + ); + if ($objectType === null) { + return 0; + } + + $sql = "SELECT COUNT(*) + FROM wcf1_like + WHERE objectTypeID = ?"; + $statement = $this->database->prepare($sql); + $statement->execute([$objectType->objectTypeID]); + + return $statement->fetchSingleColumn(); + } + + public function exportReactions(int $offset, int $limit): void + { + $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName( + 'com.woltlab.wcf.like.likeableObject', + 'de.pehbeh.links.likeableLinkEntry' + ); + if ($objectType === null) { + return; + } + + $sql = "SELECT * + FROM wcf1_like + WHERE objectTypeID = ? + ORDER BY likeID"; + $statement = $this->database->prepare($sql, $limit, $offset); + $statement->execute([$objectType->objectTypeID]); + while ($row = $statement->fetchArray()) { + $data = [ + 'objectID' => $row['objectID'], + 'objectUserID' => $row['objectUserID'], + 'userID' => $row['userID'], + 'likeValue' => $row['likeValue'], + 'time' => $row['time'], + ]; + + ImportHandler::getInstance() + ->getImporter('dev.hanashi.wsdb.links.reaction') + ->import(0, $data); + } + } + + /** + * @return array + */ + private function getLinkTags(): array + { + $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName( + 'com.woltlab.wcf.tagging.taggableObject', + 'de.pehbeh.links.linkEntry' + ); + if ($objectType === null) { + return []; + } + + $sql = "SELECT tag_to_object.objectID, + tag.name + FROM wcf1_tag tag + INNER JOIN wcf1_tag_to_object tag_to_object + ON tag_to_object.tagID = tag.tagID + WHERE tag_to_object.objectTypeID"; + $statement = $this->database->prepare($sql); + $statement->execute(); + + return $statement->fetchMap('objectID', 'name', false); + } + + /** + * @param string[] $i18nValues + * @return string[][][] + */ + private function getI18nValues(array $i18nValues): array + { + if (empty($i18nValues)) { + return []; + } + + $conditions = new PreparedStatementConditionBuilder(); + $conditions->add("language_item.languageItem IN (?)", [$i18nValues]); + + $sql = "SELECT language_item.languageItem, language_item.languageItemValue, language.languageCode + FROM wcf1_language_item language_item + LEFT JOIN wcf1_language language + ON language_item.languageID = language.languageID + " . $conditions; + $statement = $this->database->prepare($sql); + $statement->execute($conditions->getParameters()); + + $i18nValues = []; + while ($row = $statement->fetchArray()) { + $language = LanguageFactory::getInstance()->getLanguageByCode($row['languageCode']); + if ($language === null) { + continue; + } + + $languageItem = $row['languageItem']; + if (!isset($i18nValues[$languageItem])) { + $i18nValues[$languageItem] = []; + } + $i18nValues[$languageItem][$language->languageID] = $row['languageItemValue']; + } + + return $i18nValues; + } +} diff --git a/files/lib/system/wsdb/importer/LinkAttachmentImporter.class.php b/files/lib/system/wsdb/importer/LinkAttachmentImporter.class.php new file mode 100644 index 0000000..91a4511 --- /dev/null +++ b/files/lib/system/wsdb/importer/LinkAttachmentImporter.class.php @@ -0,0 +1,72 @@ +getObjectTypeByName( + 'com.woltlab.wcf.attachment.objectType', + 'com.woltlab.wsdb.record' + ); + $this->objectTypeID = $objectType->objectTypeID; + } + + #[\Override] + public function import($oldID, array $data, array $additionalData = []) + { + $recordID = ImportHandler::getInstance()->getNewID('dev.hanashi.wsdb.links.links', $data['linkID']); + $record = new Record($recordID); + if (!$record->recordID) { + return 0; + } + $content = $record->getRecordContent(); + if ($content === null) { + return 0; + } + + try { + $action = new AttachmentAction([], 'copy', [ + 'sourceObjectType' => 'de.pehbeh.links.linkEntry', + 'targetObjectType' => 'com.woltlab.wsdb.record', + 'sourceObjectID' => $data['linkID'], + 'targetObjectID' => $content->contentID, + ]); + $returnValues = $action->executeAction()['returnValues']; + $attachmentIDs = $returnValues['attachmentIDs']; + + $recordContent = $content->getData(); + foreach ($attachmentIDs as $oldAttachmentID => $attachmentID) { + if ( + ( + $newMessage = $this->fixEmbeddedAttachments( + $recordContent['description'], + $oldAttachmentID, + $attachmentID + ) + ) !== false + ) { + $recordContent['description'] = $newMessage; + } + } + + $htmlInputProcessor = new HtmlInputProcessor(); + $htmlInputProcessor->process($recordContent['description'], 'com.woltlab.wsdb.record', $content->contentID); + $recordContent['htmlInputProcessor'] = $htmlInputProcessor; + + (new SetRecordContent($record, [0 => $recordContent]))(); + } catch (\Throwable) { + } + + return 0; + } +} diff --git a/files/lib/system/wsdb/importer/LinkCategoryACLImporter.class.php b/files/lib/system/wsdb/importer/LinkCategoryACLImporter.class.php new file mode 100644 index 0000000..44eafd3 --- /dev/null +++ b/files/lib/system/wsdb/importer/LinkCategoryACLImporter.class.php @@ -0,0 +1,59 @@ +getObjectTypeByName( + 'com.woltlab.wcf.acl', + 'com.woltlab.wsdb.category' + ); + $this->objectTypeID = $objectType->objectTypeID; + + parent::__construct(); + } + + #[\Override] + public function import($oldID, array $data, array $additionalData = []) + { + if (!isset($this->options[$additionalData['optionName']])) { + return 0; + } + $data['optionID'] = $this->options[$additionalData['optionName']]; + + $data['objectID'] = ImportHandler::getInstance()->getNewID($this->objectTypeName, $data['objectID']); + if (!$data['objectID']) { + return 0; + } + + if (!empty($data['groupID'])) { + $sql = "INSERT IGNORE INTO wcf1_acl_option_to_group + (optionID, objectID, groupID, optionValue) + VALUES (?, ?, ?, ?)"; + $statement = WCF::getDB()->prepare($sql); + $statement->execute([$data['optionID'], $data['objectID'], $data['groupID'], $data['optionValue']]); + + return 1; + } elseif (!empty($data['userID'])) { + $sql = "INSERT IGNORE INTO wcf1_acl_option_to_user + (optionID, objectID, userID, optionValue) + VALUES (?, ?, ?, ?)"; + $statement = WCF::getDB()->prepare($sql); + $statement->execute([$data['optionID'], $data['objectID'], $data['userID'], $data['optionValue']]); + + return 1; + } + } +} diff --git a/files/lib/system/wsdb/importer/LinkCategoryImporter.class.php b/files/lib/system/wsdb/importer/LinkCategoryImporter.class.php new file mode 100644 index 0000000..069778b --- /dev/null +++ b/files/lib/system/wsdb/importer/LinkCategoryImporter.class.php @@ -0,0 +1,25 @@ +getNewID('dev.hanashi.wsdb.links', 1); + $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName( + 'com.woltlab.wcf.category', + 'com.woltlab.wsdb.db' . $databaseID + ); + $this->objectTypeID = $objectType->objectTypeID; + } +} diff --git a/files/lib/system/wsdb/importer/LinkCommentImporter.class.php b/files/lib/system/wsdb/importer/LinkCommentImporter.class.php new file mode 100644 index 0000000..bb341bc --- /dev/null +++ b/files/lib/system/wsdb/importer/LinkCommentImporter.class.php @@ -0,0 +1,42 @@ +getNewID('dev.hanashi.wsdb.links', 1); + $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName( + 'com.woltlab.wcf.comment.commentableContent', + 'com.woltlab.wsdb.db' . $databaseID . '.comment' + ); + $this->objectTypeID = $objectType->objectTypeID; + } + + #[\Override] + public function import($oldID, array $data, array $additionalData = []) + { + $recordID = ImportHandler::getInstance()->getNewID('dev.hanashi.wsdb.links.links', $data['objectID']); + if (!$recordID) { + return 0; + } + $data['objectID'] = $recordID; + + $comment = CommentEditor::create(\array_merge($data, ['objectTypeID' => $this->objectTypeID])); + + ImportHandler::getInstance()->saveNewID($this->objectTypeName, $oldID, $comment->commentID); + + return $comment->commentID; + } +} diff --git a/files/lib/system/wsdb/importer/LinkCommentResponseImporter.class.php b/files/lib/system/wsdb/importer/LinkCommentResponseImporter.class.php new file mode 100644 index 0000000..05e5872 --- /dev/null +++ b/files/lib/system/wsdb/importer/LinkCommentResponseImporter.class.php @@ -0,0 +1,48 @@ +getNewID($this->objectTypeName, $data['commentID']); + if (!$data['commentID']) { + return 0; + } + + $response = CommentResponseEditor::create($data); + + $sql = "SELECT responseID + FROM wcf1_comment_response + WHERE commentID = ? + ORDER BY time ASC, responseID ASC"; + $statement = WCF::getDB()->prepare($sql, 5); + $statement->execute([$response->commentID]); + $responseIDs = $statement->fetchAll(\PDO::FETCH_COLUMN); + + // update parent comment + $sql = "UPDATE wcf1_comment + SET responseIDs = ?, + responses = responses + 1 + WHERE commentID = ?"; + $statement = WCF::getDB()->prepare($sql); + $statement->execute([ + \serialize($responseIDs), + $response->commentID, + ]); + + return $response->responseID; + } +} diff --git a/files/lib/system/wsdb/importer/LinkDatabaseImporter.class.php b/files/lib/system/wsdb/importer/LinkDatabaseImporter.class.php new file mode 100644 index 0000000..760bd6a --- /dev/null +++ b/files/lib/system/wsdb/importer/LinkDatabaseImporter.class.php @@ -0,0 +1,75 @@ +getAvailablePath(); + $action = new DatabaseAction([], 'create', [ + 'data' => [ + 'name' => WCF::getLanguage()->get('wsdb.preset.dev.hanashi.links'), + 'path' => $path, + 'preset' => 'dev.hanashi.links', + 'isMultiLingual' => 0, + 'isDisabled' => 0, + 'enableLinks' => 1, + 'linksMandatory' => 1, + ], + ]); + $database = $action->executeAction()['returnValues']; + \assert($database instanceof Database); + + ImportHandler::getInstance()->resetMapping(); + ImportHandler::getInstance()->saveNewID('dev.hanashi.wsdb.links', $oldID, $database->databaseID); + + $this->setLanguage($database); + + return $database->databaseID; + } + + private function setLanguage(Database $database): void + { + $languageData = []; + foreach (LanguageFactory::getInstance()->getLanguages() as $language) { + $languageData['recordSingular'][$language->languageID] = $language->get('dev.hanashi.wsdb.links.recordSingular'); + $languageData['recordPlural'][$language->languageID] = $language->get('dev.hanashi.wsdb.links.recordPlural'); + $languageData['definiteArticle'][$language->languageID] = $language->get('dev.hanashi.wsdb.links.definiteArticle'); + } + + (new SetDatabaseLanguage($database, $languageData))(); + } + + private function getAvailablePath(?int $suffix = null): string + { + $path = 'links'; + if ($suffix !== null) { + $path .= (string)$suffix; + } + + $databaseList = new DatabaseList(); + $databaseList->getConditionBuilder()->add('path = ?', [$path]); + + if (!$databaseList->countObjects()) { + return $path; + } + + $newSuffix = 1; + if ($suffix !== null) { + $newSuffix = $suffix + $newSuffix; + } + + return $this->getAvailablePath($newSuffix); + } +} diff --git a/files/lib/system/wsdb/importer/LinkLinkImporter.class.php b/files/lib/system/wsdb/importer/LinkLinkImporter.class.php new file mode 100644 index 0000000..91e1de3 --- /dev/null +++ b/files/lib/system/wsdb/importer/LinkLinkImporter.class.php @@ -0,0 +1,87 @@ +getNewID('dev.hanashi.wsdb.links', 1); + $newCategoryID = ImportHandler::getInstance()->getNewID('dev.hanashi.wsdb.links.category', $data['categoryID']); + if (!$newCategoryID) { + return 0; + } + + $contentData = [ + 'title' => $data['subject'], + 'teaser' => '', + 'description' => $data['message'], + 'tags' => $additionalData['tags'], + ]; + + $action = new RecordAction([], 'create', [ + 'data' => [ + 'databaseID' => $databaseID, + 'categoryID' => $newCategoryID, + 'time' => $data['time'], + 'userID' => $data['userID'], + 'username' => $data['username'], + 'isDisabled' => $data['isDisabled'], + 'comments' => $data['comments'], + 'coverPhotoID' => $this->getCoverPhotoID($data['imageFile']), + 'externalURL' => $this->getLink($data), + 'reactions' => $data['cumulativeLikes'], + ], + 'content' => [ + 0 => $contentData, + ], + ]); + $record = $action->executeAction()['returnValues']; + \assert($record instanceof Record); + + ImportHandler::getInstance()->saveNewID('dev.hanashi.wsdb.links.links', $oldID, $record->recordID); + + return $record->recordID; + } + + private function getCoverPhotoID(?string $imageFile): ?int + { + if (!$imageFile) { + return null; + } + + $pathname = WCF_DIR . 'images/links/' . $imageFile; + if (!\file_exists($pathname)) { + return null; + } + + $file = FileEditor::createFromExistingFile($pathname, $imageFile, 'com.woltlab.wsdb.coverPhoto', true); + if ($file === null) { + return null; + } + + return $file->fileID; + } + + /** + * @param array $data + */ + private function getLink(array $data): string + { + if (empty($data['pageID'])) { + return $data['url']; + } + + $page = new Page($data['pageID']); + + return $page->getLink(); + } +} diff --git a/files/lib/system/wsdb/importer/LinkOptionValuesImporter.class.php b/files/lib/system/wsdb/importer/LinkOptionValuesImporter.class.php new file mode 100644 index 0000000..5b90572 --- /dev/null +++ b/files/lib/system/wsdb/importer/LinkOptionValuesImporter.class.php @@ -0,0 +1,50 @@ +getNewID('dev.hanashi.wsdb.links.links', $additionalData['linkID']); + $record = new Record($linkID); + if (!$record->recordID) { + return 0; + } + + $optionData = []; + foreach ($data as $optionValue) { + $newOptionID = ImportHandler::getInstance()->getNewID( + 'dev.hanashi.wsdb.links.option', + $optionValue['optionID'] + ); + if ($newOptionID === null) { + continue; + } + + $val = $optionValue['optionValue']; + if (\in_array($optionValue['optionType'], ['checkboxes', 'multiSelect'])) { + $val = \implode(',', \explode("\n", StringUtil::unifyNewlines($val))); + } + + if ($val != '') { + $optionData['option' . $newOptionID] = $val; + } + } + + (new SetRecordOptionValues( + $record, + $optionData, + [] + ))(); + + return 0; + } +} diff --git a/files/lib/system/wsdb/importer/LinkOptionsImporter.class.php b/files/lib/system/wsdb/importer/LinkOptionsImporter.class.php new file mode 100644 index 0000000..0c80db4 --- /dev/null +++ b/files/lib/system/wsdb/importer/LinkOptionsImporter.class.php @@ -0,0 +1,102 @@ + + */ + private array $optionTypeMap = [ + 'boolean' => 'boolean', + 'checkboxes' => 'checkboxes', + 'date' => 'date', + 'integer' => 'integer', + 'float' => 'float', + 'multiSelect' => 'checkboxes', + 'radioButton' => 'radioButton', + 'select' => 'select', + 'text' => 'text', + 'textarea' => 'textarea', + 'URL' => 'url', + ]; + + #[\Override] + public function import($oldID, array $data, array $additionalData = []): int + { + $databaseID = ImportHandler::getInstance()->getNewID('dev.hanashi.wsdb.links', 1); + if (!$databaseID) { + return 0; + } + + if (!isset($this->optionTypeMap[$data['optionType']])) { + return 0; + } + + $configuration = []; + if ($data['required']) { + $configuration['required'] = 1; + } + if (!empty($data['selectOptions'])) { + $selectOptions = []; + $lines = \explode("\n", StringUtil::unifyNewlines($data['selectOptions'])); + foreach ($lines as $line) { + $lineSplitted = \explode(':', $line, 2); + if (\count($lineSplitted) == 2) { + $selectOptions[] = [ + 'key' => $lineSplitted[0], + 'value' => [$lineSplitted[1]], + ]; + } else { + $selectOptions[] = [ + 'key' => $lineSplitted[0], + 'value' => [$lineSplitted[0]], + ]; + } + } + $configuration['selectOptions'] = JSON::encode($selectOptions); + } + + $action = new OptionAction([], 'create', [ + 'data' => [ + 'databaseID' => $databaseID, + 'showOrder' => $data['showOrder'], + 'name' => $data['optionTitle'], + 'description' => $data['optionDescription'], + 'optionType' => $this->optionTypeMap[$data['optionType']], + 'configuration' => JSON::encode($configuration), + 'displayPosition' => 'descriptionList', + 'isDisabled' => $data['isDisabled'], + 'isLimitedToCategories' => $additionalData['categories'] === [] ? 0 : 1, + ], + ]); + $newOption = $action->executeAction()['returnValues']; + \assert($newOption instanceof Option); + + ImportHandler::getInstance()->saveNewID('dev.hanashi.wsdb.links.option', $oldID, $newOption->optionID); + + $sql = "INSERT INTO wcf1_wsdb_option_to_record_category + (optionID, categoryID) + VALUES (?, ?)"; + $statement = WCF::getDB()->prepare($sql); + + foreach ($additionalData['categories'] as $categoryID) { + $newCategoryID = ImportHandler::getInstance()->getNewID('dev.hanashi.wsdb.links.category', $categoryID); + if (!$newCategoryID) { + continue; + } + + $statement->execute([$newOption->optionID, $newCategoryID]); + } + + return $newOption->optionID; + } +} diff --git a/files/lib/system/wsdb/importer/LinkPermissionImporter.class.php b/files/lib/system/wsdb/importer/LinkPermissionImporter.class.php new file mode 100644 index 0000000..1440365 --- /dev/null +++ b/files/lib/system/wsdb/importer/LinkPermissionImporter.class.php @@ -0,0 +1,25 @@ +getObjectTypeByName( + 'com.woltlab.wcf.acl', + 'com.woltlab.wsdb.database' + ); + $this->objectTypeID = $objectType->objectTypeID; + + parent::__construct(); + } +} diff --git a/files/lib/system/wsdb/importer/LinkReactionImporter.class.php b/files/lib/system/wsdb/importer/LinkReactionImporter.class.php new file mode 100644 index 0000000..690e3e2 --- /dev/null +++ b/files/lib/system/wsdb/importer/LinkReactionImporter.class.php @@ -0,0 +1,66 @@ +getNewID('dev.hanashi.wsdb.links', 1); + $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName( + 'com.woltlab.wcf.like.likeableObject', + 'com.woltlab.wsdb.db' . $databaseID . '.reaction' + ); + $this->objectTypeID = $objectType->objectTypeID; + } + + #[\Override] + public function import($oldID, array $data, array $additionalData = []) + { + $data['objectID'] = ImportHandler::getInstance()->getNewID('dev.hanashi.wsdb.links.links', $data['objectID']); + if (!$data['objectID']) { + return 0; + } + + if (empty($data['time'])) { + $data['time'] = 1; + } + + if (!isset($data['reactionTypeID'])) { + if ($data['likeValue'] == 1) { + $data['reactionTypeID'] = ReactionHandler::getInstance()->getFirstReactionTypeID(); + } else { + $data['reactionTypeID'] = self::getDislikeReactionTypeID(); + } + } else { + $data['reactionTypeID'] = ImportHandler::getInstance() + ->getNewID('com.woltlab.wcf.reactionType', $data['reactionTypeID']); + } + + if (empty($data['reactionTypeID'])) { + return 0; + } + + $sql = "INSERT IGNORE INTO wcf1_like + (objectID, objectTypeID, objectUserID, userID, time, likeValue, reactionTypeID) + VALUES (?, ?, ?, ?, ?, ?, ?)"; + $statement = WCF::getDB()->prepare($sql); + $statement->execute([ + $data['objectID'], + $this->objectTypeID, + $data['objectUserID'], + $data['userID'], + $data['time'], + $data['likeValue'], + $data['reactionTypeID'], + ]); + + return 0; + } +} diff --git a/language/de.xml b/language/de.xml index fd147d4..0297ca4 100644 --- a/language/de.xml +++ b/language/de.xml @@ -10,6 +10,23 @@ + + + + + + WoltLab Suite Database]]> + + + + + + + + + + + diff --git a/language/en.xml b/language/en.xml index 3efb513..dd1f05c 100644 --- a/language/en.xml +++ b/language/en.xml @@ -10,6 +10,23 @@ + + + + + + WoltLab Suite Database]]> + + + + + + + + + + + diff --git a/objectType.xml b/objectType.xml new file mode 100644 index 0000000..dcfa6a6 --- /dev/null +++ b/objectType.xml @@ -0,0 +1,65 @@ + + + + + dev.hanashi.wsdb.links + com.woltlab.wcf.exporter + wcf\system\wsdb\exporter\LinkDatabaseExporter + + + dev.hanashi.wsdb.links + com.woltlab.wcf.importer + wcf\system\wsdb\importer\LinkDatabaseImporter + + + dev.hanashi.wsdb.links.category + com.woltlab.wcf.importer + wcf\system\wsdb\importer\LinkCategoryImporter + + + dev.hanashi.wsdb.links.permission + com.woltlab.wcf.importer + wcf\system\wsdb\importer\LinkPermissionImporter + + + dev.hanashi.wsdb.links.category.acl + com.woltlab.wcf.importer + wcf\system\wsdb\importer\LinkCategoryACLImporter + + + dev.hanashi.wsdb.links.option + com.woltlab.wcf.importer + wcf\system\wsdb\importer\LinkOptionsImporter + + + dev.hanashi.wsdb.links.links + com.woltlab.wcf.importer + wcf\system\wsdb\importer\LinkLinkImporter + + + dev.hanashi.wsdb.links.option.values + com.woltlab.wcf.importer + wcf\system\wsdb\importer\LinkOptionValuesImporter + + + dev.hanashi.wsdb.links.comment + com.woltlab.wcf.importer + wcf\system\wsdb\importer\LinkCommentImporter + + + dev.hanashi.wsdb.links.comment.response + com.woltlab.wcf.importer + wcf\system\wsdb\importer\LinkCommentResponseImporter + + + dev.hanashi.wsdb.links.reaction + com.woltlab.wcf.importer + wcf\system\wsdb\importer\LinkReactionImporter + + + dev.hanashi.wsdb.links.attachments + com.woltlab.wcf.importer + wcf\system\wsdb\importer\LinkAttachmentImporter + + + diff --git a/package.xml b/package.xml index 4d409a0..3577e2b 100644 --- a/package.xml +++ b/package.xml @@ -27,6 +27,7 @@ + acp/database/install_dev.hanashi.wsdb.links.php acp/install_dev.hanashi.wsdb.links.php