
- also integrated better support for newContent types - Integrated saving posts to database (posts gotten via outbox request as well as posts received in the NewContent endpoint) - proper support for handling comments - support for likes/dislikes - support for requesting followers / following endpoints - better inbox support database needs changes, don't forget to run migration
230 lines
7.2 KiB
PHP
230 lines
7.2 KiB
PHP
<?php
|
|
/**
|
|
* SPDX-FileCopyrightText: 2024 Sascha Nitsch (grumpydeveloper) https://contentnation.net/@grumpydevelop
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* @author Sascha Nitsch (grumpydeveloper)
|
|
**/
|
|
|
|
namespace Federator\DIO;
|
|
|
|
/**
|
|
* IO functions related to users
|
|
*/
|
|
class Posts
|
|
{
|
|
/**
|
|
* get posts by user
|
|
*
|
|
* @param \mysqli $dbh @unused-param
|
|
* database handle
|
|
* @param string $userid
|
|
* user id
|
|
* @param \Federator\Connector\Connector $connector
|
|
* connector to fetch use with
|
|
* @param \Federator\Cache\Cache|null $cache
|
|
* optional caching service
|
|
* @param string $min
|
|
* minimum date
|
|
* @param string $max
|
|
* maximum date
|
|
* @return \Federator\Data\ActivityPub\Common\Activity[]
|
|
*/
|
|
public static function getPostsByUser($dbh, $userid, $connector, $cache, $min, $max)
|
|
{
|
|
// ask cache
|
|
if ($cache !== null) {
|
|
$posts = $cache->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;
|
|
}
|
|
}
|