forked from grumpydevelop/federator
		
	refactored ContentNation data-format
- also fixed issue where we didn't retrieve posts from the DB - also fixed issue where posts from db were malformatted - changed contentnation data-converter for comments & votes to support the new data-structure (more consistent, easier to read)
This commit is contained in:
		
							parent
							
								
									d355b5a7cd
								
							
						
					
					
						commit
						ba88adcebd
					
				
					 4 changed files with 109 additions and 93 deletions
				
			
		| 
						 | 
				
			
			@ -89,6 +89,11 @@ class NewContent implements \Federator\Api\APIInterface
 | 
			
		|||
 | 
			
		||||
        $input = is_string($_rawInput) ? json_decode($_rawInput, true) : null;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        $dbh = $this->main->getDatabase();
 | 
			
		||||
        $cache = $this->main->getCache();
 | 
			
		||||
        $connector = $this->main->getConnector();
 | 
			
		||||
 | 
			
		||||
        $config = $this->main->getConfig();
 | 
			
		||||
        $domain = $config['generic']['externaldomain'];
 | 
			
		||||
        if (!is_array($input)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -97,21 +102,16 @@ class NewContent implements \Federator\Api\APIInterface
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        if (isset($allHeaders['X-Sender'])) {
 | 
			
		||||
            $newActivity = $this->main->getConnector()->jsonToActivity($input);
 | 
			
		||||
            $newActivity = $connector->jsonToActivity($input);
 | 
			
		||||
        } else {
 | 
			
		||||
            error_log("NewContent::post No X-Sender header found");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($newActivity === false) {
 | 
			
		||||
            error_log("NewContent::post couldn't create newActivity");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $dbh = $this->main->getDatabase();
 | 
			
		||||
        $cache = $this->main->getCache();
 | 
			
		||||
        $connector = $this->main->getConnector();
 | 
			
		||||
 | 
			
		||||
        if (!isset($_user)) {
 | 
			
		||||
            $user = $newActivity->getAActor(); // url of the sender https://contentnation.net/username
 | 
			
		||||
            $user = str_replace(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -117,7 +117,7 @@ class Factory
 | 
			
		|||
                $return = new Common\Undo();
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                error_log("newActivityFromJson " . print_r($json, true));
 | 
			
		||||
                error_log("newActivityFromJson unsupported type: " . print_r($json, true));
 | 
			
		||||
        }
 | 
			
		||||
        if (isset($return) && $return->fromJson($json) !== null) {
 | 
			
		||||
            return $return;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,6 +43,7 @@ class Posts
 | 
			
		|||
        if ($posts === false) {
 | 
			
		||||
            $posts = [];
 | 
			
		||||
        }
 | 
			
		||||
        echo "Found " . count($posts) . " posts in DB\n";
 | 
			
		||||
 | 
			
		||||
        // Only override $min if we found posts in our DB
 | 
			
		||||
        $remoteMin = $min;
 | 
			
		||||
| 
						 | 
				
			
			@ -102,15 +103,15 @@ class Posts
 | 
			
		|||
     */
 | 
			
		||||
    public static function getPostsFromDb($dbh, $userId, $min = null, $max = null)
 | 
			
		||||
    {
 | 
			
		||||
        $sql = 'SELECT id, user_id, type, object, published FROM posts WHERE user_id = ?';
 | 
			
		||||
        $sql = 'SELECT `id`, `url`, `user_id`, `actor`, `type`, `object`, `to`, `cc`, `published` FROM posts WHERE user_id = ?';
 | 
			
		||||
        $params = [$userId];
 | 
			
		||||
        $types = 's';
 | 
			
		||||
        if ($min !== null) {
 | 
			
		||||
        if ($min !== null && $min !== "") {
 | 
			
		||||
            $sql .= ' AND published >= ?';
 | 
			
		||||
            $params[] = $min;
 | 
			
		||||
            $types .= 's';
 | 
			
		||||
        }
 | 
			
		||||
        if ($max !== null) {
 | 
			
		||||
        if ($max !== null && $max !== "") {
 | 
			
		||||
            $sql .= ' AND published <= ?';
 | 
			
		||||
            $params[] = $max;
 | 
			
		||||
            $types .= 's';
 | 
			
		||||
| 
						 | 
				
			
			@ -130,16 +131,33 @@ class Posts
 | 
			
		|||
        }
 | 
			
		||||
        $posts = [];
 | 
			
		||||
        while ($row = $result->fetch_assoc()) {
 | 
			
		||||
            if (!empty($row['object'])) {
 | 
			
		||||
                $objectData = json_decode($row['object'], true);
 | 
			
		||||
                if (is_array($objectData)) {
 | 
			
		||||
                    // Use the ActivityPub factory to create the APObject
 | 
			
		||||
                    $object = \Federator\Data\ActivityPub\Factory::newActivityFromJson($objectData);
 | 
			
		||||
                    if ($object !== false) {
 | 
			
		||||
                        $posts[] = $object;
 | 
			
		||||
                    }
 | 
			
		||||
            if (isset($row['to']) && $row['to'] !== null) {
 | 
			
		||||
                $row['to'] = json_decode($row['to'], true);
 | 
			
		||||
            }
 | 
			
		||||
            if (isset($row['cc']) && $row['cc'] !== null) {
 | 
			
		||||
                $row['cc'] = json_decode($row['cc'], true);
 | 
			
		||||
            }
 | 
			
		||||
            if (isset($row['object']) && $row['object'] !== null) {
 | 
			
		||||
                $decoded = json_decode($row['object'], true);
 | 
			
		||||
                // Only use decoded value if it's an array/object
 | 
			
		||||
                if (is_array($decoded)) {
 | 
			
		||||
                    $row['object'] = $decoded;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (isset($row['published']) && $row['published'] !== null) {
 | 
			
		||||
                // If it's numeric, keep as int. If it's a string, try to parse as ISO 8601.
 | 
			
		||||
                if (is_numeric($row['published'])) {
 | 
			
		||||
                    $row['published'] = intval($row['published'], 10);
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Try to parse as datetime string
 | 
			
		||||
                    $timestamp = strtotime($row['published']);
 | 
			
		||||
                    $row['published'] = $timestamp !== false ? $timestamp : null;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            $activity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($row);
 | 
			
		||||
            if ($activity !== false) {
 | 
			
		||||
                $posts[] = $activity;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $stmt->close();
 | 
			
		||||
        return $posts;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -151,6 +151,7 @@ class ContentNation implements Connector
 | 
			
		|||
                            ->addTo("https://www.w3.org/ns/activitystreams#Public")
 | 
			
		||||
                            ->addCC('https://' . $domain . '/' . $userId . '/followers');
 | 
			
		||||
                        $create->setURL('https://' . $domain . '/' . $activity['profilename'] . '/' . $activity['name']);
 | 
			
		||||
                        $create->setID('https://' . $domain . '/' . $activity['profilename'] . '/' . $activity['id']);
 | 
			
		||||
                        $apArticle = new \Federator\Data\ActivityPub\Common\Article();
 | 
			
		||||
                        if (array_key_exists('tags', $activity)) {
 | 
			
		||||
                            foreach ($activity['tags'] as $tag) {
 | 
			
		||||
| 
						 | 
				
			
			@ -207,16 +208,23 @@ class ContentNation implements Connector
 | 
			
		|||
                        $commentJson = $activity;
 | 
			
		||||
                        $commentJson['type'] = 'Note';
 | 
			
		||||
                        $commentJson['summary'] = $activity['subject'];
 | 
			
		||||
                        $commentJson['id'] = $activity['id'];
 | 
			
		||||
                        $commentJson['id'] = 'https://' . $domain . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName'] . '#' . $activity['id'];
 | 
			
		||||
                        $note = \Federator\Data\ActivityPub\Factory::newFromJson($commentJson, "");
 | 
			
		||||
                        if ($note === null) {
 | 
			
		||||
                            error_log("ContentNation::getRemotePostsByUser couldn't create comment");
 | 
			
		||||
                            $comment = new \Federator\Data\ActivityPub\Common\Activity('Comment');
 | 
			
		||||
                            $create->setObject($comment);
 | 
			
		||||
                            $note = new \Federator\Data\ActivityPub\Common\Activity('Comment');
 | 
			
		||||
                            $create->setObject($note);
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                        $url = 'https://' . $domain . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName'] . '#' . $note->getID();
 | 
			
		||||
                        $note->setID($commentJson['id']);
 | 
			
		||||
                        if (!isset($commentJson['parent']) || $commentJson['parent'] === null) {
 | 
			
		||||
                            $note->setInReplyTo('https://' . $domain . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName']);
 | 
			
		||||
                        } elseif ($replyType === "comment") {
 | 
			
		||||
                            $note->setInReplyTo('https://' . $domain . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName'] . "#" . $commentJson['parent']);
 | 
			
		||||
                        }
 | 
			
		||||
                        $url = 'https://' . $domain . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName'] . '#' . $activity['id'];
 | 
			
		||||
                        $create->setURL($url);
 | 
			
		||||
                        $create->setID($url);
 | 
			
		||||
                        $create->setObject($note);
 | 
			
		||||
                        $posts[] = $create;
 | 
			
		||||
                        break; // Comment
 | 
			
		||||
| 
						 | 
				
			
			@ -227,9 +235,9 @@ class ContentNation implements Connector
 | 
			
		|||
                        $like = new \Federator\Data\ActivityPub\Common\Activity($likeType);
 | 
			
		||||
                        $like->setAActor('https://' . $domain . '/' . $userId);
 | 
			
		||||
                        $like->setID($activity['id'])
 | 
			
		||||
                            ->setPublished($activity['published'] ?? $activity['timestamp'])
 | 
			
		||||
                            ->addTo("https://www.w3.org/ns/activitystreams#Public")
 | 
			
		||||
                            ->addCC('https://' . $domain . '/' . $userId . '/followers');
 | 
			
		||||
                            ->setPublished($activity['published'] ?? $activity['timestamp']);
 | 
			
		||||
                        // $like->addTo("https://www.w3.org/ns/activitystreams#Public")
 | 
			
		||||
                            // ->addCC('https://' . $domain . '/' . $userId . '/followers');
 | 
			
		||||
                        $like->setSummary(
 | 
			
		||||
                            $this->main->translate(
 | 
			
		||||
                                $activity['articlelang'],
 | 
			
		||||
| 
						 | 
				
			
			@ -239,10 +247,8 @@ class ContentNation implements Connector
 | 
			
		|||
                            )
 | 
			
		||||
                        );
 | 
			
		||||
                        $objectUrl = 'https://' . $domain . '/' . $userId . '/' . $activity['articlename'];
 | 
			
		||||
                        if ($activity['comment'] !== '') {
 | 
			
		||||
                            $objectUrl .= "#" . $activity['comment'];
 | 
			
		||||
                        }
 | 
			
		||||
                        $like->setURL($objectUrl . '#' . $activity['id']);
 | 
			
		||||
                        $like->setID($objectUrl . '#' . $activity['id']);
 | 
			
		||||
                        $like->setObject($objectUrl);
 | 
			
		||||
                        $posts[] = $like;
 | 
			
		||||
                        break; // Vote
 | 
			
		||||
| 
						 | 
				
			
			@ -364,6 +370,7 @@ class ContentNation implements Connector
 | 
			
		|||
     */
 | 
			
		||||
    public function jsonToActivity(array $jsonData)
 | 
			
		||||
    {
 | 
			
		||||
        $returnActivity = false;
 | 
			
		||||
        // Common fields for all activity types
 | 
			
		||||
        $ap = [
 | 
			
		||||
            '@context' => 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
| 
						 | 
				
			
			@ -372,52 +379,65 @@ class ContentNation implements Connector
 | 
			
		|||
            'actor' => $jsonData['actor'] ?? null,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        $config = $this->main->getConfig();
 | 
			
		||||
        $domain = $config['generic']['externaldomain'];
 | 
			
		||||
 | 
			
		||||
        $ourUrl = 'https://' . $domain;
 | 
			
		||||
 | 
			
		||||
        // Extract actorName as the last segment of the actor URL (after the last '/')
 | 
			
		||||
        $actorUrl = $jsonData['actor'] ?? null;
 | 
			
		||||
        $actorName = null;
 | 
			
		||||
        $replaceUrl = null;
 | 
			
		||||
        if (is_array($actorUrl)) {
 | 
			
		||||
            $actorUrl = $actorUrl['id'];
 | 
			
		||||
            $replaceUrl = $actorUrl->url ?? null;
 | 
			
		||||
        }
 | 
			
		||||
        if ($actorUrl !== null) {
 | 
			
		||||
            $actorUrlParts = parse_url($actorUrl);
 | 
			
		||||
            if (isset($actorUrlParts['path'])) {
 | 
			
		||||
                $pathSegments = array_values(array_filter(explode('/', $actorUrlParts['path'])));
 | 
			
		||||
                $actorName = end($pathSegments);
 | 
			
		||||
                // Build replaceUrl as scheme://host if both are set
 | 
			
		||||
                if (isset($actorUrlParts['scheme'], $actorUrlParts['host'])) {
 | 
			
		||||
                    $replaceUrl = $actorUrlParts['scheme'] . '://' . $actorUrlParts['host'];
 | 
			
		||||
                } else {
 | 
			
		||||
                    $replaceUrl = $actorUrl;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                $actorName = $actorUrl;
 | 
			
		||||
                $replaceUrl = $actorUrl;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $ap['actor'] = $actorUrl;
 | 
			
		||||
        $actorData = $jsonData['actor'] ?? null;
 | 
			
		||||
        $actorName = $actorData['name'] ?? null;
 | 
			
		||||
 | 
			
		||||
        $ap['actor'] = $ourUrl . '/' . $actorName;
 | 
			
		||||
 | 
			
		||||
        // Handle specific fields based on the type
 | 
			
		||||
        switch ($jsonData['type']) {
 | 
			
		||||
            case 'comment':
 | 
			
		||||
                $commentId = $jsonData['object']['id'] ?? null;
 | 
			
		||||
                $articleName = $jsonData['object']['articleName'] ?? null;
 | 
			
		||||
                $articleOwnerName = $jsonData['object']['articleOwnerName'] ?? null;
 | 
			
		||||
                // Set Create-level fields
 | 
			
		||||
                $ap['published'] = $jsonData['object']['published'] ?? null;
 | 
			
		||||
                $ap['url'] = $jsonData['object']['url'] ?? null;
 | 
			
		||||
                $ap['to'] = ['https://www.w3.org/ns/activitystreams#Public'];
 | 
			
		||||
                $ap['cc'] = [$jsonData['related']['cc']['followers'] ?? null];
 | 
			
		||||
 | 
			
		||||
                // Set object as Note with only required fields
 | 
			
		||||
                $ap['id'] = $ourUrl . "/" . $articleOwnerName . "/" . $articleName . "#" . $commentId;
 | 
			
		||||
                $ap['url'] = $ourUrl . "/" . $articleOwnerName . "/" . $articleName . "#" . $commentId;
 | 
			
		||||
                $ap['type'] = 'Create';
 | 
			
		||||
                $ap['object'] = [
 | 
			
		||||
                    'id' => $jsonData['object']['id'] ?? null,
 | 
			
		||||
                    'type' => 'Note',
 | 
			
		||||
                    'content' => $jsonData['object']['content'] ?? '',
 | 
			
		||||
                    'summary' => $jsonData['object']['summary'] ?? '',
 | 
			
		||||
                    'id' => $ourUrl . "/" . $articleOwnerName . "/" . $articleName . "#" . $commentId,
 | 
			
		||||
                    'content' => $jsonData['object']['content'] ?? null,
 | 
			
		||||
                    'summary' => $jsonData['object']['summary'] ?? null,
 | 
			
		||||
                ];
 | 
			
		||||
                $ap['cc'] = ['https://www.w3.org/ns/activitystreams#Public'];
 | 
			
		||||
                if (isset($jsonData['options'])) {
 | 
			
		||||
                    if (isset($jsonData['options']['informFollowers'])) {
 | 
			
		||||
                        if ($jsonData['options']['informFollowers'] === true) {
 | 
			
		||||
                            if ($actorName != $articleOwnerName) {
 | 
			
		||||
                                $ap['to'][] = $ourUrl . '/' . $articleOwnerName;
 | 
			
		||||
                            }
 | 
			
		||||
                            $ap['to'][] = $ourUrl . '/' . $actorName . '/followers';
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                $replyType = $jsonData['object']['inReplyTo']['type'] ?? null;
 | 
			
		||||
                if ($replyType === "article") {
 | 
			
		||||
                    $ap['object']['inReplyTo'] = $ourUrl . "/" . $articleOwnerName . "/" . $articleName;
 | 
			
		||||
                } elseif ($replyType === "comment") {
 | 
			
		||||
                    $ap['object']['inReplyTo'] = $ourUrl . "/" . $articleOwnerName . "/" . $articleName . "#" . $jsonData['object']['inReplyTo']['id'];
 | 
			
		||||
                } else {
 | 
			
		||||
                    error_log("ContentNation::jsonToActivity unknown inReplyTo type: {$replyType}");
 | 
			
		||||
                }
 | 
			
		||||
                $returnActivity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($ap);
 | 
			
		||||
                $returnActivity->setID($ap['id']);
 | 
			
		||||
                $returnActivity->setURL($ap['url']);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case 'vote':
 | 
			
		||||
                $ap['id'] .= "_$actorName";
 | 
			
		||||
                $votedOn = $jsonData['object']['type'] ?? null;
 | 
			
		||||
                $articleName = $jsonData['object']['articleName'] ?? null;
 | 
			
		||||
                $articleOwnerName = $jsonData['object']['articleOwnerName'] ?? null;
 | 
			
		||||
                $voteId = $jsonData['object']['id'] ?? null;
 | 
			
		||||
                $ap['id'] = $ourUrl . '/' . $articleOwnerName . '/' . $articleName . '#' . $voteId;
 | 
			
		||||
                $ap['url'] = $ourUrl . '/' . $articleOwnerName . '/' . $articleName . '#' . $voteId;
 | 
			
		||||
                if (
 | 
			
		||||
                    isset($jsonData['vote']['type']) &&
 | 
			
		||||
                    strtolower($jsonData['vote']['type']) === 'undo'
 | 
			
		||||
| 
						 | 
				
			
			@ -432,7 +452,10 @@ class ContentNation implements Connector
 | 
			
		|||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $objectId = $jsonData['object']['id'] ?? null;
 | 
			
		||||
                $objectId = $ourUrl . '/' . $articleOwnerName . '/' . $articleName;
 | 
			
		||||
                if ($votedOn === "comment") {
 | 
			
		||||
                    $objectId .= '#' . $jsonData['object']['commentId'];
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $ap['object'] = $objectId;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -462,43 +485,18 @@ class ContentNation implements Connector
 | 
			
		|||
                        'author' => $jsonData['object']['author'] ?? null,
 | 
			
		||||
                    ];
 | 
			
		||||
                } */
 | 
			
		||||
 | 
			
		||||
                $returnActivity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($ap);
 | 
			
		||||
                $returnActivity->setID($ap['id']);
 | 
			
		||||
                $returnActivity->setURL($ap['url']);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            default:
 | 
			
		||||
                // Handle unsupported types or fallback to default behavior
 | 
			
		||||
                throw new \InvalidArgumentException("Unsupported activity type: {$jsonData['type']}");
 | 
			
		||||
                throw new \InvalidArgumentException("ContentNation::jsonToActivity Unsupported activity type: {$jsonData['type']}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $config = $this->main->getConfig();
 | 
			
		||||
        $domain = $config['generic']['externaldomain'];
 | 
			
		||||
        /**
 | 
			
		||||
         * Recursively replace strings in an array.
 | 
			
		||||
         *
 | 
			
		||||
         * @param string $search
 | 
			
		||||
         * @param string $replace
 | 
			
		||||
         * @param array<mixed, mixed> $array
 | 
			
		||||
         * @return array<mixed, mixed>
 | 
			
		||||
         */
 | 
			
		||||
        function array_str_replace_recursive(string $search, string $replace, array $array): array
 | 
			
		||||
        {
 | 
			
		||||
            foreach ($array as $key => $value) {
 | 
			
		||||
                if (is_array($value)) {
 | 
			
		||||
                    $array[$key] = array_str_replace_recursive($search, $replace, $value);
 | 
			
		||||
                } elseif (is_string($value)) {
 | 
			
		||||
                    $array[$key] = str_replace($search, $replace, $value);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return $array;
 | 
			
		||||
        }
 | 
			
		||||
        if (is_string($replaceUrl)) {
 | 
			
		||||
            $ap = array_str_replace_recursive(
 | 
			
		||||
                $replaceUrl,
 | 
			
		||||
                'https://' . $domain,
 | 
			
		||||
                $ap
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return \Federator\Data\ActivityPub\Factory::newActivityFromJson($ap);
 | 
			
		||||
        return $returnActivity;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue