getRemotePostsByUser($userid, $min, $max); if ($posts !== false) { return $posts; } } $posts = self::getPostsFromDb($dbh, $userid, $min, $max); if ($posts === false) { $posts = []; } // Only override $min if we found posts in our DB $remoteMin = $min; if (!empty($posts)) { // Find the latest published date in the DB posts $latestPublished = null; foreach ($posts as $post) { $published = $post->getPublished(); if ($published != null) { $publishedStr = gmdate('Y-m-d H:i:s', $published); if ($latestPublished === null || $publishedStr > $latestPublished) { $latestPublished = $publishedStr; } } } if ($latestPublished !== null) { $remoteMin = $latestPublished; } } // Always fetch newer posts from connector (if any) $newPosts = $connector->getRemotePostsByUser($userid, $remoteMin, $max); if ($newPosts !== false && is_array($newPosts)) { // Merge new posts with DB posts, avoiding duplicates by ID $existingIds = []; foreach ($posts as $post) { $existingIds[$post->getID()] = true; } foreach ($newPosts as $newPost) { if (!isset($existingIds[$newPost->getID()])) { $posts[] = $newPost; } } } // save posts to DB foreach ($posts as $post) { if ($post->getID() !== "") { self::savePost($dbh, $userid, $post); } } if ($cache !== null) { $cache->saveRemotePostsByUser($userid, $posts); } return $posts; } /** * Get posts for a user from the DB (optionally by date) * * @param \mysqli $dbh * @param string $userId * @param string|null $min * @param string|null $max * @return \Federator\Data\ActivityPub\Common\Activity[]|false */ public static function getPostsFromDb($dbh, $userId, $min = null, $max = null) { $sql = 'SELECT id, user_id, type, object, published FROM posts WHERE user_id = ?'; $params = [$userId]; $types = 's'; if ($min !== null) { $sql .= ' AND published >= ?'; $params[] = $min; $types .= 's'; } if ($max !== null) { $sql .= ' AND published <= ?'; $params[] = $max; $types .= 's'; } $sql .= ' ORDER BY published DESC'; $stmt = $dbh->prepare($sql); if ($stmt === false) { throw new \Federator\Exceptions\ServerError(); } $stmt->bind_param($types, ...$params); $stmt->execute(); $result = $stmt->get_result(); if (!($result instanceof \mysqli_result)) { $stmt->close(); return false; } $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; } } } } $stmt->close(); return $posts; } /** * Save a post (insert or update) * * @param \mysqli $dbh * @param string $userId * @param \Federator\Data\ActivityPub\Common\Activity $post * @return bool */ public static function savePost($dbh, $userId, $post) { $sql = 'INSERT INTO posts ( `id`, `url`, `user_id`, `actor`, `type`, `object`, `to`, `cc`, `published` ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE `url` = VALUES(`url`), `user_id` = VALUES(`user_id`), `actor` = VALUES(`actor`), `type` = VALUES(`type`), `object` = VALUES(`object`), `to` = VALUES(`to`), `cc` = VALUES(`cc`), `published` = VALUES(`published`)'; $stmt = $dbh->prepare($sql); if ($stmt === false) { throw new \Federator\Exceptions\ServerError(); } $id = $post->getID(); $url = $post->getUrl(); $actor = $post->getAActor(); $type = $post->getType(); $object = $post->getObject(); $objectJson = ($object instanceof \Federator\Data\ActivityPub\Common\APObject) ? json_encode($object) : $object; if ($objectJson === false) { $objectJson = null; } $to = $post->getTo(); $cc = $post->getCC(); $toJson = is_array($to) ? json_encode($to) : (is_string($to) ? json_encode([$to]) : null); $ccJson = is_array($cc) ? json_encode($cc) : (is_string($cc) ? json_encode([$cc]) : null); $published = $post->getPublished(); $publishedStr = $published ? gmdate('Y-m-d H:i:s', $published) : gmdate('Y-m-d H:i:s'); $stmt->bind_param( "sssssssss", $id, $url, $userId, $actor, $type, $objectJson, $toJson, $ccJson, $publishedStr ); $result = $stmt->execute(); $stmt->close(); return $result; } /** * Delete a post * * @param \mysqli $dbh * @param string $id The post ID * @return bool */ public static function deletePost($dbh, $id) { $sql = 'delete from posts where id = ?'; $stmt = $dbh->prepare($sql); if ($stmt === false) { throw new \Federator\Exceptions\ServerError(); } $stmt->bind_param("s", $id); $stmt->execute(); $affectedRows = $stmt->affected_rows; $stmt->close(); return $affectedRows > 0; } }