From 834844573bd9ad0ddd9b1caa59252f0468b36d5c Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 20 Apr 2022 06:28:02 +0000 Subject: [PATCH] We can now store incoming questions --- database.sql | 48 +++++++++++++++++++- doc/database.md | 2 + doc/database/db_post-question-option.md | 30 +++++++++++++ doc/database/db_post-question.md | 32 ++++++++++++++ src/Model/Post/Question.php | 57 ++++++++++++++++++++++++ src/Model/Post/QuestionOption.php | 59 +++++++++++++++++++++++++ src/Protocol/ActivityPub/Processor.php | 33 ++++++++++++++ src/Protocol/ActivityPub/Receiver.php | 15 +++++-- static/dbstructure.config.php | 28 +++++++++++- static/dbview.config.php | 20 +++++++++ 10 files changed, 318 insertions(+), 6 deletions(-) create mode 100644 doc/database/db_post-question-option.md create mode 100644 doc/database/db_post-question.md create mode 100644 src/Model/Post/Question.php create mode 100644 src/Model/Post/QuestionOption.php diff --git a/database.sql b/database.sql index 78fd9620e2..48a75377fe 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2022.05-dev (Siberian Iris) --- DB_UPDATE_VERSION 1457 +-- DB_UPDATE_VERSION 1458 -- ------------------------------------------ @@ -1161,6 +1161,32 @@ CREATE TABLE IF NOT EXISTS `post-media` ( FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Attached media'; +-- +-- TABLE post-question +-- +CREATE TABLE IF NOT EXISTS `post-question` ( + `id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID', + `uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri', + `multiple` boolean NOT NULL DEFAULT '0' COMMENT 'Multiple choice', + `voters` int unsigned COMMENT 'Number of voters for this question', + `end-time` datetime DEFAULT '0001-01-01 00:00:00' COMMENT 'Question end time', + PRIMARY KEY(`id`), + UNIQUE INDEX `uri-id` (`uri-id`), + FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE +) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Question'; + +-- +-- TABLE post-question-option +-- +CREATE TABLE IF NOT EXISTS `post-question-option` ( + `id` int unsigned NOT NULL COMMENT 'Id of the question', + `uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri', + `name` varchar(255) COMMENT 'Name of the option', + `replies` int unsigned COMMENT 'Number of replies for this question option', + PRIMARY KEY(`uri-id`,`id`), + FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE +) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Question option'; + -- -- TABLE post-tag -- @@ -1720,6 +1746,10 @@ CREATE VIEW `post-user-view` AS SELECT `event`.`type` AS `event-type`, `event`.`nofinish` AS `event-nofinish`, `event`.`ignore` AS `event-ignore`, + `post-question`.`id` AS `question-id`, + `post-question`.`multiple` AS `question-multiple`, + `post-question`.`voters` AS `question-voters`, + `post-question`.`end-time` AS `question-end-time`, `diaspora-interaction`.`interaction` AS `signed_text`, `parent-item-uri`.`guid` AS `parent-guid`, `parent-post`.`network` AS `parent-network`, @@ -1745,6 +1775,7 @@ CREATE VIEW `post-user-view` AS SELECT LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-user`.`uri-id` LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post-user`.`uri-id` LEFT JOIN `post-delivery-data` ON `post-delivery-data`.`uri-id` = `post-user`.`uri-id` AND `post-user`.`origin` + LEFT JOIN `post-question` ON `post-question`.`uri-id` = `post-user`.`uri-id` LEFT JOIN `permissionset` ON `permissionset`.`id` = `post-user`.`psid` LEFT JOIN `post-user` AS `parent-post` ON `parent-post`.`uri-id` = `post-user`.`parent-uri-id` AND `parent-post`.`uid` = `post-user`.`uid` LEFT JOIN `contact` AS `parent-post-author` ON `parent-post-author`.`id` = `parent-post`.`author-id`; @@ -1880,6 +1911,10 @@ CREATE VIEW `post-thread-user-view` AS SELECT `event`.`type` AS `event-type`, `event`.`nofinish` AS `event-nofinish`, `event`.`ignore` AS `event-ignore`, + `post-question`.`id` AS `question-id`, + `post-question`.`multiple` AS `question-multiple`, + `post-question`.`voters` AS `question-voters`, + `post-question`.`end-time` AS `question-end-time`, `diaspora-interaction`.`interaction` AS `signed_text`, `parent-item-uri`.`guid` AS `parent-guid`, `parent-post`.`network` AS `parent-network`, @@ -1904,6 +1939,7 @@ CREATE VIEW `post-thread-user-view` AS SELECT LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-thread-user`.`uri-id` LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post-thread-user`.`uri-id` LEFT JOIN `post-delivery-data` ON `post-delivery-data`.`uri-id` = `post-thread-user`.`uri-id` AND `post-thread-user`.`origin` + LEFT JOIN `post-question` ON `post-question`.`uri-id` = `post-thread-user`.`uri-id` LEFT JOIN `permissionset` ON `permissionset`.`id` = `post-thread-user`.`psid` LEFT JOIN `post-user` AS `parent-post` ON `parent-post`.`uri-id` = `post-user`.`parent-uri-id` AND `parent-post`.`uid` = `post-thread-user`.`uid` LEFT JOIN `contact` AS `parent-post-author` ON `parent-post-author`.`id` = `parent-post`.`author-id`; @@ -2006,6 +2042,10 @@ CREATE VIEW `post-view` AS SELECT `causer`.`blocked` AS `causer-blocked`, `causer`.`hidden` AS `causer-hidden`, `causer`.`contact-type` AS `causer-contact-type`, + `post-question`.`id` AS `question-id`, + `post-question`.`multiple` AS `question-multiple`, + `post-question`.`voters` AS `question-voters`, + `post-question`.`end-time` AS `question-end-time`, `diaspora-interaction`.`interaction` AS `signed_text`, `parent-item-uri`.`guid` AS `parent-guid`, `parent-post`.`network` AS `parent-network`, @@ -2027,6 +2067,7 @@ CREATE VIEW `post-view` AS SELECT LEFT JOIN `verb` ON `verb`.`id` = `post`.`vid` LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post`.`uri-id` LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post`.`uri-id` + LEFT JOIN `post-question` ON `post-question`.`uri-id` = `post`.`uri-id` LEFT JOIN `post` AS `parent-post` ON `parent-post`.`uri-id` = `post`.`parent-uri-id` LEFT JOIN `contact` AS `parent-post-author` ON `parent-post-author`.`id` = `parent-post`.`author-id`; @@ -2128,6 +2169,10 @@ CREATE VIEW `post-thread-view` AS SELECT `causer`.`blocked` AS `causer-blocked`, `causer`.`hidden` AS `causer-hidden`, `causer`.`contact-type` AS `causer-contact-type`, + `post-question`.`id` AS `question-id`, + `post-question`.`multiple` AS `question-multiple`, + `post-question`.`voters` AS `question-voters`, + `post-question`.`end-time` AS `question-end-time`, `diaspora-interaction`.`interaction` AS `signed_text`, `parent-item-uri`.`guid` AS `parent-guid`, `parent-post`.`network` AS `parent-network`, @@ -2149,6 +2194,7 @@ CREATE VIEW `post-thread-view` AS SELECT LEFT JOIN `verb` ON `verb`.`id` = `post`.`vid` LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-thread`.`uri-id` LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post-thread`.`uri-id` + LEFT JOIN `post-question` ON `post-question`.`uri-id` = `post-thread`.`uri-id` LEFT JOIN `post` AS `parent-post` ON `parent-post`.`uri-id` = `post`.`parent-uri-id` LEFT JOIN `contact` AS `parent-post-author` ON `parent-post-author`.`id` = `parent-post`.`author-id`; diff --git a/doc/database.md b/doc/database.md index d436dcc32b..629a86ee0a 100644 --- a/doc/database.md +++ b/doc/database.md @@ -52,6 +52,8 @@ Database Tables | [post-delivery-data](help/database/db_post-delivery-data) | Delivery data for items | | [post-link](help/database/db_post-link) | Post related external links | | [post-media](help/database/db_post-media) | Attached media | +| [post-question](help/database/db_post-question) | Question | +| [post-question-option](help/database/db_post-question-option) | Question option | | [post-tag](help/database/db_post-tag) | post relation to tags | | [post-thread](help/database/db_post-thread) | Thread related data | | [post-thread-user](help/database/db_post-thread-user) | Thread related data per user | diff --git a/doc/database/db_post-question-option.md b/doc/database/db_post-question-option.md new file mode 100644 index 0000000000..0dd10acabf --- /dev/null +++ b/doc/database/db_post-question-option.md @@ -0,0 +1,30 @@ +Table post-question-option +=========== + +Question option + +Fields +------ + +| Field | Description | Type | Null | Key | Default | Extra | +| ------- | --------------------------------------------------------- | ------------ | ---- | --- | ------- | ----- | +| id | Id of the question | int unsigned | NO | PRI | NULL | | +| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | PRI | NULL | | +| name | Name of the option | varchar(255) | YES | | NULL | | +| replies | Number of replies for this question option | int unsigned | YES | | NULL | | + +Indexes +------------ + +| Name | Fields | +| ------- | ---------- | +| PRIMARY | uri-id, id | + +Foreign Keys +------------ + +| Field | Target Table | Target Field | +|-------|--------------|--------------| +| uri-id | [item-uri](help/database/db_item-uri) | id | + +Return to [database documentation](help/database) diff --git a/doc/database/db_post-question.md b/doc/database/db_post-question.md new file mode 100644 index 0000000000..8f637ffcf3 --- /dev/null +++ b/doc/database/db_post-question.md @@ -0,0 +1,32 @@ +Table post-question +=========== + +Question + +Fields +------ + +| Field | Description | Type | Null | Key | Default | Extra | +| -------- | --------------------------------------------------------- | ------------ | ---- | --- | ------------------- | -------------- | +| id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment | +| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | | NULL | | +| multiple | Multiple choice | boolean | NO | | 0 | | +| voters | Number of voters for this question | int unsigned | YES | | NULL | | +| end-time | Question end time | datetime | YES | | 0001-01-01 00:00:00 | | + +Indexes +------------ + +| Name | Fields | +| ------- | -------------- | +| PRIMARY | id | +| uri-id | UNIQUE, uri-id | + +Foreign Keys +------------ + +| Field | Target Table | Target Field | +|-------|--------------|--------------| +| uri-id | [item-uri](help/database/db_item-uri) | id | + +Return to [database documentation](help/database) diff --git a/src/Model/Post/Question.php b/src/Model/Post/Question.php new file mode 100644 index 0000000000..949ff2ec5e --- /dev/null +++ b/src/Model/Post/Question.php @@ -0,0 +1,57 @@ +. + * + */ + +namespace Friendica\Model\Post; + +use \BadMethodCallException; +use Friendica\Database\Database; +use Friendica\Database\DBA; +use Friendica\Database\DBStructure; + +class Question +{ + /** + * Update a post question entry + * + * @param integer $uri_id + * @param array $data + * @param bool $insert_if_missing + * @return bool + * @throws \Exception + */ + public static function update(int $uri_id, array $data = [], bool $insert_if_missing = true) + { + if (empty($uri_id)) { + throw new BadMethodCallException('Empty URI_id'); + } + + $fields = DBStructure::getFieldsForTable('post-question', $data); + + // Remove the key fields + unset($fields['uri-id']); + + if (empty($fields)) { + return true; + } + + return DBA::update('post-question', $fields, ['uri-id' => $uri_id], $insert_if_missing ? true : []); + } +} diff --git a/src/Model/Post/QuestionOption.php b/src/Model/Post/QuestionOption.php new file mode 100644 index 0000000000..d5e82d57dd --- /dev/null +++ b/src/Model/Post/QuestionOption.php @@ -0,0 +1,59 @@ +. + * + */ + +namespace Friendica\Model\Post; + +use \BadMethodCallException; +use Friendica\Database\Database; +use Friendica\Database\DBA; +use Friendica\Database\DBStructure; + +class QuestionOption +{ + /** + * Update a post question-option entry + * + * @param integer $uri_id + * @param integer $id + * @param array $data + * @param bool $insert_if_missing + * @return bool + * @throws \Exception + */ + public static function update(int $uri_id, int $id, array $data = [], bool $insert_if_missing = true) + { + if (empty($uri_id)) { + throw new BadMethodCallException('Empty URI_id'); + } + + $fields = DBStructure::getFieldsForTable('post-question-option', $data); + + // Remove the key fields + unset($fields['uri-id']); + unset($fields['id']); + + if (empty($fields)) { + return true; + } + + return DBA::update('post-question-option', $fields, ['uri-id' => $uri_id, 'id' => $id], $insert_if_missing ? true : []); + } +} diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index 8bd08b95b4..be17ea469c 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -152,6 +152,37 @@ class Processor } } + /** + * Store attachment data + * + * @param array $activity + * @param array $item + */ + private static function storeQuestion($activity, $item) + { + if (empty($activity['question'])) { + return; + } + $question = ['multiple' => $activity['question']['multiple']]; + + if (!empty($activity['question']['voters'])) { + $question['voters'] = $activity['question']['voters']; + } + + if (!empty($activity['question']['end-time'])) { + $question['end-time'] = $activity['question']['end-time']; + } + + Post\Question::update($item['uri-id'], $question); + + foreach ($activity['question']['options'] as $key => $option) { + $option = ['name' => $option['name'], 'replies' => $option['replies']]; + Post\QuestionOption::update($item['uri-id'], $key, $option); + } + + Logger::debug('Storing incoming question', ['type' => $activity['type'], 'uri-id' => $item['uri-id'], 'question' => $activity['question']]); + } + /** * Updates a message * @@ -178,6 +209,7 @@ class Processor $item = self::processContent($activity, $item); self::storeAttachments($activity, $item); + self::storeQuestion($activity, $item); if (empty($item)) { return; @@ -347,6 +379,7 @@ class Processor $item['plink'] = $activity['alternate-url'] ?? $item['uri']; self::storeAttachments($activity, $item); + self::storeQuestion($activity, $item); // We received the post via AP, so we set the protocol of the server to AP $contact = Contact::getById($item['author-id'], ['gsid']); diff --git a/src/Protocol/ActivityPub/Receiver.php b/src/Protocol/ActivityPub/Receiver.php index 6cd0111ec0..c8d131b725 100644 --- a/src/Protocol/ActivityPub/Receiver.php +++ b/src/Protocol/ActivityPub/Receiver.php @@ -553,12 +553,13 @@ class Receiver $object_data['from-relay'] = $activity['from-relay']; } + if (in_array('as:Question', [$object_data['object_type'] ?? '', $object_data['object_object_type'] ?? ''])) { + self::storeUnhandledActivity(false, $type, $object_data, $activity, $body, $uid, $trust_source, $push, $signer); + } + switch ($type) { case 'as:Create': if (in_array($object_data['object_type'], self::CONTENT_TYPES)) { - if ($object_data['object_type'] == 'as:Question') { - self::storeUnhandledActivity(false, $type, $object_data, $activity, $body, $uid, $trust_source, $push, $signer); - } $item = ActivityPub\Processor::createItem($object_data); ActivityPub\Processor::postItem($object_data, $item); } elseif (in_array($object_data['object_type'], ['pt:CacheFile'])) { @@ -1460,7 +1461,13 @@ class Receiver return []; } - // @todo Check if "closed" is a thing, see here: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-closed + $closed = JsonLD::fetchElement($object, 'as:closed', '@value'); + if (!empty($closed)) { + $question['end-time'] = $closed; + } else { + $question['end-time'] = JsonLD::fetchElement($object, 'as:endTime', '@value'); + } + $question['voters'] = (int)JsonLD::fetchElement($object, 'toot:votersCount', '@value'); $question['options'] = []; diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index aea4837566..8c2f4dcf90 100644 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -55,7 +55,7 @@ use Friendica\Database\DBA; if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1457); + define('DB_UPDATE_VERSION', 1458); } return [ @@ -1202,6 +1202,32 @@ return [ "uri-id-url" => ["UNIQUE", "uri-id", "url"], ] ], + "post-question" => [ + "comment" => "Question", + "fields" => [ + "id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"], + "uri-id" => ["type" => "int unsigned", "not null" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"], + "multiple" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Multiple choice"], + "voters" => ["type" => "int unsigned", "comment" => "Number of voters for this question"], + "end-time" => ["type" => "datetime", "default" => DBA::NULL_DATETIME, "comment" => "Question end time"], + ], + "indexes" => [ + "PRIMARY" => ["id"], + "uri-id" => ["UNIQUE", "uri-id"], + ] + ], + "post-question-option" => [ + "comment" => "Question option", + "fields" => [ + "id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "comment" => "Id of the question"], + "uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"], + "name" => ["type" => "varchar(255)", "comment" => "Name of the option"], + "replies" => ["type" => "int unsigned", "comment" => "Number of replies for this question option"], + ], + "indexes" => [ + "PRIMARY" => ["uri-id", "id"], + ] + ], "post-tag" => [ "comment" => "post relation to tags", "fields" => [ diff --git a/static/dbview.config.php b/static/dbview.config.php index 9083185d5a..82ccaa9d12 100644 --- a/static/dbview.config.php +++ b/static/dbview.config.php @@ -186,6 +186,10 @@ "event-type" => ["event", "type"], "event-nofinish" => ["event", "nofinish"], "event-ignore" => ["event", "ignore"], + "question-id" => ["post-question", "id"], + "question-multiple" => ["post-question", "multiple"], + "question-voters" => ["post-question", "voters"], + "question-end-time" => ["post-question", "end-time"], "signed_text" => ["diaspora-interaction", "interaction"], "parent-guid" => ["parent-item-uri", "guid"], "parent-network" => ["parent-post", "network"], @@ -212,6 +216,7 @@ LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-user`.`uri-id` LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post-user`.`uri-id` LEFT JOIN `post-delivery-data` ON `post-delivery-data`.`uri-id` = `post-user`.`uri-id` AND `post-user`.`origin` + LEFT JOIN `post-question` ON `post-question`.`uri-id` = `post-user`.`uri-id` LEFT JOIN `permissionset` ON `permissionset`.`id` = `post-user`.`psid` LEFT JOIN `post-user` AS `parent-post` ON `parent-post`.`uri-id` = `post-user`.`parent-uri-id` AND `parent-post`.`uid` = `post-user`.`uid` LEFT JOIN `contact` AS `parent-post-author` ON `parent-post-author`.`id` = `parent-post`.`author-id`" @@ -344,6 +349,10 @@ "event-type" => ["event", "type"], "event-nofinish" => ["event", "nofinish"], "event-ignore" => ["event", "ignore"], + "question-id" => ["post-question", "id"], + "question-multiple" => ["post-question", "multiple"], + "question-voters" => ["post-question", "voters"], + "question-end-time" => ["post-question", "end-time"], "signed_text" => ["diaspora-interaction", "interaction"], "parent-guid" => ["parent-item-uri", "guid"], "parent-network" => ["parent-post", "network"], @@ -369,6 +378,7 @@ LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-thread-user`.`uri-id` LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post-thread-user`.`uri-id` LEFT JOIN `post-delivery-data` ON `post-delivery-data`.`uri-id` = `post-thread-user`.`uri-id` AND `post-thread-user`.`origin` + LEFT JOIN `post-question` ON `post-question`.`uri-id` = `post-thread-user`.`uri-id` LEFT JOIN `permissionset` ON `permissionset`.`id` = `post-thread-user`.`psid` LEFT JOIN `post-user` AS `parent-post` ON `parent-post`.`uri-id` = `post-user`.`parent-uri-id` AND `parent-post`.`uid` = `post-thread-user`.`uid` LEFT JOIN `contact` AS `parent-post-author` ON `parent-post-author`.`id` = `parent-post`.`author-id`" @@ -468,6 +478,10 @@ "causer-blocked" => ["causer", "blocked"], "causer-hidden" => ["causer", "hidden"], "causer-contact-type" => ["causer", "contact-type"], + "question-id" => ["post-question", "id"], + "question-multiple" => ["post-question", "multiple"], + "question-voters" => ["post-question", "voters"], + "question-end-time" => ["post-question", "end-time"], "signed_text" => ["diaspora-interaction", "interaction"], "parent-guid" => ["parent-item-uri", "guid"], "parent-network" => ["parent-post", "network"], @@ -490,6 +504,7 @@ LEFT JOIN `verb` ON `verb`.`id` = `post`.`vid` LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post`.`uri-id` LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post`.`uri-id` + LEFT JOIN `post-question` ON `post-question`.`uri-id` = `post`.`uri-id` LEFT JOIN `post` AS `parent-post` ON `parent-post`.`uri-id` = `post`.`parent-uri-id` LEFT JOIN `contact` AS `parent-post-author` ON `parent-post-author`.`id` = `parent-post`.`author-id`" ], @@ -588,6 +603,10 @@ "causer-blocked" => ["causer", "blocked"], "causer-hidden" => ["causer", "hidden"], "causer-contact-type" => ["causer", "contact-type"], + "question-id" => ["post-question", "id"], + "question-multiple" => ["post-question", "multiple"], + "question-voters" => ["post-question", "voters"], + "question-end-time" => ["post-question", "end-time"], "signed_text" => ["diaspora-interaction", "interaction"], "parent-guid" => ["parent-item-uri", "guid"], "parent-network" => ["parent-post", "network"], @@ -610,6 +629,7 @@ LEFT JOIN `verb` ON `verb`.`id` = `post`.`vid` LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-thread`.`uri-id` LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post-thread`.`uri-id` + LEFT JOIN `post-question` ON `post-question`.`uri-id` = `post-thread`.`uri-id` LEFT JOIN `post` AS `parent-post` ON `parent-post`.`uri-id` = `post`.`parent-uri-id` LEFT JOIN `contact` AS `parent-post-author` ON `parent-post-author`.`id` = `parent-post`.`author-id`" ], -- 2.39.5