forked from grumpydevelop/federator
		
	initial support for articles from CN
- fixed how To and CC field (recipients) are handled in general - fixed posts in database - improved some error exceptions and prevented early breaks through try-catch blocks - we now support CN-articles on our newcontent endpoint, with create and update calls
This commit is contained in:
		
							parent
							
								
									ba88adcebd
								
							
						
					
					
						commit
						62cfd6ef0d
					
				
					 12 changed files with 398 additions and 150 deletions
				
			
		| 
						 | 
				
			
			@ -43,7 +43,7 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
 | 
			
		|||
    /**
 | 
			
		||||
     * handle post call
 | 
			
		||||
     *
 | 
			
		||||
     * @param string|null $_user user to add data to inbox
 | 
			
		||||
     * @param string|null $_user user to add data to inbox @unused-param
 | 
			
		||||
     * @return string|false response
 | 
			
		||||
     */
 | 
			
		||||
    public function post($_user)
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +59,12 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
 | 
			
		|||
 | 
			
		||||
        $activity = is_string($_rawInput) ? json_decode($_rawInput, true) : null;
 | 
			
		||||
 | 
			
		||||
        $dbh = $this->main->getDatabase();
 | 
			
		||||
        $cache = $this->main->getCache();
 | 
			
		||||
        $connector = $this->main->getConnector();
 | 
			
		||||
 | 
			
		||||
        $config = $this->main->getConfig();
 | 
			
		||||
 | 
			
		||||
        if (!is_array($activity)) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("Inbox::post Input wasn't of type array");
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -68,33 +74,33 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
 | 
			
		|||
        if ($inboxActivity === false) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("Inbox::post couldn't create inboxActivity");
 | 
			
		||||
        }
 | 
			
		||||
        $user = $inboxActivity->getAActor(); // url of the sender https://contentnation.net/username
 | 
			
		||||
        $username = basename((string) (parse_url($user, PHP_URL_PATH) ?? ''));
 | 
			
		||||
        $domain = parse_url($user, PHP_URL_HOST);
 | 
			
		||||
 | 
			
		||||
        $rootDir = PROJECT_ROOT . '/';
 | 
			
		||||
        $users = [];
 | 
			
		||||
 | 
			
		||||
        // Shared inbox
 | 
			
		||||
        if (!isset($_user)) {
 | 
			
		||||
            // Save the raw input and parsed JSON to a file for inspection
 | 
			
		||||
            file_put_contents(
 | 
			
		||||
                $rootDir . 'logs/inbox.log',
 | 
			
		||||
                date('Y-m-d H:i:s') . ": ==== POST Inbox Activity ====\n" . json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
 | 
			
		||||
                FILE_APPEND
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        $receivers = array_merge($inboxActivity->getTo(), $inboxActivity->getCC());
 | 
			
		||||
 | 
			
		||||
        $sendTo = $inboxActivity->getCC();
 | 
			
		||||
        if ($inboxActivity->getType() === 'Undo') { // for undo the object holds the proper cc
 | 
			
		||||
        // For Undo, the object may hold the proper to/cc
 | 
			
		||||
        if ($inboxActivity->getType() === 'Undo') {
 | 
			
		||||
            $object = $inboxActivity->getObject();
 | 
			
		||||
            if ($object !== null && is_object($object)) {
 | 
			
		||||
                $sendTo = $object->getCC();
 | 
			
		||||
                $receivers = array_merge($object->getTo(), $object->getCC());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $users = [];
 | 
			
		||||
        $dbh = $this->main->getDatabase();
 | 
			
		||||
        $cache = $this->main->getCache();
 | 
			
		||||
        $connector = $this->main->getConnector();
 | 
			
		||||
        // Filter out the public address and keep only actual URLs
 | 
			
		||||
        $receivers = array_filter($receivers, static function (mixed $receiver): bool {
 | 
			
		||||
            return is_string($receiver)
 | 
			
		||||
                && $receiver !== 'https://www.w3.org/ns/activitystreams#Public'
 | 
			
		||||
                && (filter_var($receiver, FILTER_VALIDATE_URL) !== false);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        foreach ($sendTo as $receiver) {
 | 
			
		||||
        if (!in_array($username, $receivers, true)) {
 | 
			
		||||
            $receivers[] = $username;
 | 
			
		||||
        }
 | 
			
		||||
        foreach ($receivers as $receiver) {
 | 
			
		||||
            if ($receiver === '' || !is_string($receiver)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -110,25 +116,68 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
 | 
			
		|||
                $username = basename((string) (parse_url($actor, PHP_URL_PATH) ?? ''));
 | 
			
		||||
                $domain = parse_url($actor, PHP_URL_HOST);
 | 
			
		||||
                if ($username === null || $domain === null) {
 | 
			
		||||
                    error_log("Inbox::post no username or domain found");
 | 
			
		||||
                    error_log("Inbox::post no username or domain found for recipient: $receiver");
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                try {
 | 
			
		||||
                    $followers = \Federator\DIO\Followers::getFollowersByFedUser($dbh, $connector, $cache, $username . '@' . $domain);
 | 
			
		||||
                } catch (\Throwable $e) {
 | 
			
		||||
                    error_log("Inbox::post get followers for user: " . $username . '@' . $domain . ". Exception: " . $e->getMessage());
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                $followers = \Federator\DIO\Followers::getFollowersByFedUser($dbh, $connector, $cache, $username . '@' . $domain);
 | 
			
		||||
 | 
			
		||||
                if (is_array($followers)) {
 | 
			
		||||
                    $users = array_merge($users, array_column($followers, 'id'));
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                $ourDomain = $config['generic']['externaldomain'];
 | 
			
		||||
                // check if receiver is an actor url from our domain
 | 
			
		||||
                if (!str_contains($receiver, $ourDomain)) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                $receiverName = basename((string) (parse_url($receiver, PHP_URL_PATH) ?? ''));
 | 
			
		||||
                $domain = parse_url($receiver, PHP_URL_HOST);
 | 
			
		||||
                if ($receiverName === null || $domain === null) {
 | 
			
		||||
                    error_log("Inbox::post no receiverName or domain found for receiver: " . $receiver);
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                $receiver = $receiverName . '@' . $domain;
 | 
			
		||||
                try {
 | 
			
		||||
                    $user = \Federator\DIO\User::getUserByName(
 | 
			
		||||
                        $dbh,
 | 
			
		||||
                        $receiver,
 | 
			
		||||
                        $connector,
 | 
			
		||||
                        $cache
 | 
			
		||||
                    );
 | 
			
		||||
                } catch (\Throwable $e) {
 | 
			
		||||
                    error_log("Inbox::post get user by name: " . $receiver . ". Exception: " . $e->getMessage());
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                if ($user === null || $user->id === null) {
 | 
			
		||||
                    error_log("Inbox::post couldn't find user: $receiver");
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                $users[] = $user->id;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if ($_user !== false && !in_array($_user, $users, true)) {
 | 
			
		||||
            $users[] = $_user;
 | 
			
		||||
 | 
			
		||||
        if (empty($users)) { // todo remove after proper implementation, debugging for now
 | 
			
		||||
            $rootDir = PROJECT_ROOT . '/';
 | 
			
		||||
            // Save the raw input and parsed JSON to a file for inspection
 | 
			
		||||
            file_put_contents(
 | 
			
		||||
                $rootDir . 'logs/inbox.log',
 | 
			
		||||
                date('Y-m-d H:i:s') . ": ==== POST Inbox Activity ====\n" . json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
 | 
			
		||||
                FILE_APPEND
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        foreach ($users as $user) {
 | 
			
		||||
 | 
			
		||||
        foreach ($users as $receiver) {
 | 
			
		||||
            if (!isset($user)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            $token = \Resque::enqueue('inbox', 'Federator\\Jobs\\InboxJob', [
 | 
			
		||||
                'user' => $user,
 | 
			
		||||
                'user' => $username . '@' . $domain,
 | 
			
		||||
                'recipientId' => $receiver,
 | 
			
		||||
                'activity' => $inboxActivity->toObject(),
 | 
			
		||||
            ]);
 | 
			
		||||
            error_log("Inbox::post enqueued job for user: $user with token: $token");
 | 
			
		||||
| 
						 | 
				
			
			@ -142,32 +191,46 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
 | 
			
		|||
     * @param \mysqli $dbh database handle
 | 
			
		||||
     * @param \Federator\Connector\Connector $connector connector to use
 | 
			
		||||
     * @param \Federator\Cache\Cache|null $cache optional caching service
 | 
			
		||||
     * @param string $_user user to add data to inbox
 | 
			
		||||
     * @param string $_user user that triggered the post
 | 
			
		||||
     * @param string $_recipientId recipient of the post
 | 
			
		||||
     * @param \Federator\Data\ActivityPub\Common\Activity $inboxActivity the activity that we received
 | 
			
		||||
     * @return boolean response
 | 
			
		||||
     */
 | 
			
		||||
    public static function postForUser($dbh, $connector, $cache, $_user, $inboxActivity)
 | 
			
		||||
    public static function postForUser($dbh, $connector, $cache, $_user, $_recipientId, $inboxActivity)
 | 
			
		||||
    {
 | 
			
		||||
        if (!isset($_user)) {
 | 
			
		||||
            error_log("Inbox::postForUser no user given");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $user = \Federator\DIO\User::getUserByName(
 | 
			
		||||
        // get sender
 | 
			
		||||
        $user = \Federator\DIO\FedUser::getUserByName(
 | 
			
		||||
            $dbh,
 | 
			
		||||
            $_user,
 | 
			
		||||
            $connector,
 | 
			
		||||
            $cache
 | 
			
		||||
        );
 | 
			
		||||
        if ($user === null || $user->id === null) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("Inbox::postForUser couldn't find user: $_user");
 | 
			
		||||
            error_log("Inbox::postForUser couldn't find user: $_user");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // get recipient
 | 
			
		||||
        $recipient = \Federator\DIO\User::getUserByName(
 | 
			
		||||
            $dbh,
 | 
			
		||||
            $_recipientId,
 | 
			
		||||
            $connector,
 | 
			
		||||
            $cache
 | 
			
		||||
        );
 | 
			
		||||
        if ($recipient === null || $recipient->id === null) {
 | 
			
		||||
            error_log("Inbox::postForUser couldn't find user: $_recipientId");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $rootDir = PROJECT_ROOT . '/';
 | 
			
		||||
        // Save the raw input and parsed JSON to a file for inspection
 | 
			
		||||
        file_put_contents(
 | 
			
		||||
            $rootDir . 'logs/inbox_' . $_user . '.log',
 | 
			
		||||
            date('Y-m-d H:i:s') . ": ==== POST " . $_user . " Inbox Activity ====\n" . json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
 | 
			
		||||
            $rootDir . 'logs/inbox_' . $recipient->id . '.log',
 | 
			
		||||
            date('Y-m-d H:i:s') . ": ==== POST " . $recipient->id . " Inbox Activity ====\n" . json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
 | 
			
		||||
            FILE_APPEND
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -195,12 +258,15 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
 | 
			
		|||
                $object = $inboxActivity->getObject();
 | 
			
		||||
                if (is_string($object)) {
 | 
			
		||||
                    \Federator\DIO\Posts::deletePost($dbh, $object);
 | 
			
		||||
                } elseif (is_object($object)) {
 | 
			
		||||
                    $objectId = $object->getID();
 | 
			
		||||
                    \Federator\DIO\Posts::deletePost($dbh, $objectId);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case 'Undo':
 | 
			
		||||
                $object = $inboxActivity->getObject();
 | 
			
		||||
                if (is_object($object) && method_exists($object, 'getType')) {
 | 
			
		||||
                if (is_object($object)) {
 | 
			
		||||
                    switch ($object->getType()) {
 | 
			
		||||
                        case 'Follow':
 | 
			
		||||
                            $success = false;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -77,7 +77,6 @@ class NewContent implements \Federator\Api\APIInterface
 | 
			
		|||
    public function post($_user)
 | 
			
		||||
    {
 | 
			
		||||
        $_rawInput = file_get_contents('php://input');
 | 
			
		||||
 | 
			
		||||
        $allHeaders = getallheaders();
 | 
			
		||||
        try {
 | 
			
		||||
            $this->main->checkSignature($allHeaders);
 | 
			
		||||
| 
						 | 
				
			
			@ -111,27 +110,94 @@ class NewContent implements \Federator\Api\APIInterface
 | 
			
		|||
            error_log("NewContent::post couldn't create newActivity");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!isset($_user)) {
 | 
			
		||||
            $user = $newActivity->getAActor(); // url of the sender https://contentnation.net/username
 | 
			
		||||
            $user = str_replace(
 | 
			
		||||
            $posterName = str_replace(
 | 
			
		||||
                $domain,
 | 
			
		||||
                '',
 | 
			
		||||
                $user
 | 
			
		||||
            ); // retrieve only the last part of the url
 | 
			
		||||
        } else {
 | 
			
		||||
            $user = $dbh->real_escape_string($_user);
 | 
			
		||||
            $posterName = $dbh->real_escape_string($_user);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $users = [];
 | 
			
		||||
 | 
			
		||||
        if ($newActivity->getType() === 'Create') {
 | 
			
		||||
            $followers = $this->fetchAllFollowers($dbh, $connector, $cache, $user);
 | 
			
		||||
        }
 | 
			
		||||
        if (!empty($followers)) {
 | 
			
		||||
            $users = array_merge($users, $followers);
 | 
			
		||||
        $receivers = array_merge($newActivity->getTo(), $newActivity->getCC());
 | 
			
		||||
 | 
			
		||||
        // For Undo, the object may hold the proper to/cc
 | 
			
		||||
        if ($newActivity->getType() === 'Undo') {
 | 
			
		||||
            $object = $newActivity->getObject();
 | 
			
		||||
            if ($object !== null && is_object($object)) {
 | 
			
		||||
                $receivers = array_merge($object->getTo(), $object->getCC());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Filter out the public address and keep only actual URLs
 | 
			
		||||
        $receivers = array_filter($receivers, static function (mixed $receiver): bool {
 | 
			
		||||
            return is_string($receiver)
 | 
			
		||||
                && $receiver !== 'https://www.w3.org/ns/activitystreams#Public'
 | 
			
		||||
                && (filter_var($receiver, FILTER_VALIDATE_URL) !== false);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (!in_array($posterName, $receivers, true)) {
 | 
			
		||||
            $receivers[] = $posterName;
 | 
			
		||||
        }
 | 
			
		||||
        foreach ($receivers as $receiver) {
 | 
			
		||||
            if ($receiver === '' || !is_string($receiver)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (str_ends_with($receiver, '/followers')) {
 | 
			
		||||
                $actor = $newActivity->getAActor();
 | 
			
		||||
                if ($actor === null || !is_string($actor)) {
 | 
			
		||||
                    error_log("NewContent::post no actor found");
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if ($posterName === null) {
 | 
			
		||||
                    error_log("NewContent::post no username found");
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                try {
 | 
			
		||||
                    $followers = \Federator\DIO\Followers::getFollowersByUser($dbh, $posterName, $connector, $cache);
 | 
			
		||||
                } catch (\Throwable $e) {
 | 
			
		||||
                    error_log("NewContent::post get followers for user: " . $posterName . ". Exception: " . $e->getMessage());
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (is_array($followers)) {
 | 
			
		||||
                    $users = array_merge($users, array_column($followers, 'id'));
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // check if receiver is an actor url and not from our domain
 | 
			
		||||
                if (str_contains($receiver, $domain)) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                $receiverName = basename((string) (parse_url($receiver, PHP_URL_PATH) ?? ''));
 | 
			
		||||
                $domain = parse_url($receiver, PHP_URL_HOST);
 | 
			
		||||
                if ($receiverName === null || $domain === null) {
 | 
			
		||||
                    error_log("NewContent::post no receiverName or domain found for receiver: " . $receiver);
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                $receiver = $receiverName . '@' . $domain;
 | 
			
		||||
                try {
 | 
			
		||||
                    $user = \Federator\DIO\FedUser::getUserByName(
 | 
			
		||||
                        $dbh,
 | 
			
		||||
                        $receiver,
 | 
			
		||||
                        $cache
 | 
			
		||||
                    );
 | 
			
		||||
                } catch (\Throwable $e) {
 | 
			
		||||
                    error_log("NewContent::post get user by name: " . $receiver . ". Exception: " . $e->getMessage());
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                if ($user === null || $user->id === null) {
 | 
			
		||||
                    error_log("NewContent::post couldn't find user: $receiver");
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                $users[] = $user->id;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (empty($users)) { // todo remove after proper implementation, debugging for now
 | 
			
		||||
            $rootDir = PROJECT_ROOT . '/';
 | 
			
		||||
            // Save the raw input and parsed JSON to a file for inspection
 | 
			
		||||
| 
						 | 
				
			
			@ -141,20 +207,18 @@ class NewContent implements \Federator\Api\APIInterface
 | 
			
		|||
                FILE_APPEND
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        if ($_user !== false && !in_array($_user, $users, true)) {
 | 
			
		||||
            $users[] = $_user;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach ($users as $user) {
 | 
			
		||||
            if (!isset($user)) {
 | 
			
		||||
        foreach ($users as $receiver) {
 | 
			
		||||
            if (!isset($receiver)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $token = \Resque::enqueue('inbox', 'Federator\\Jobs\\NewContentJob', [
 | 
			
		||||
                'user' => $user,
 | 
			
		||||
                'user' => $posterName,
 | 
			
		||||
                'recipientId' => $receiver,
 | 
			
		||||
                'activity' => $newActivity->toObject(),
 | 
			
		||||
            ]);
 | 
			
		||||
            error_log("Inbox::post enqueued job for user: $user with token: $token");
 | 
			
		||||
            error_log("Inbox::post enqueued job for receiver: $receiver with token: $token");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return json_encode($newActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
 | 
			
		||||
| 
						 | 
				
			
			@ -170,17 +234,18 @@ class NewContent implements \Federator\Api\APIInterface
 | 
			
		|||
     * @param \Federator\Cache\Cache|null $cache
 | 
			
		||||
     *          optional caching service
 | 
			
		||||
     * @param string $_user user that triggered the post
 | 
			
		||||
     * @param string $_recipientId recipient of the post
 | 
			
		||||
     * @param \Federator\Data\ActivityPub\Common\Activity $newActivity the activity that we received
 | 
			
		||||
     * @return boolean response
 | 
			
		||||
     */
 | 
			
		||||
    public static function postForUser($dbh, $connector, $cache, $_user, $newActivity)
 | 
			
		||||
    public static function postForUser($dbh, $connector, $cache, $_user, $_recipientId, $newActivity)
 | 
			
		||||
    {
 | 
			
		||||
        if (!isset($_user)) {
 | 
			
		||||
            error_log("NewContent::postForUser no user given");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // get user
 | 
			
		||||
        // get sender
 | 
			
		||||
        $user = \Federator\DIO\User::getUserByName(
 | 
			
		||||
            $dbh,
 | 
			
		||||
            $_user,
 | 
			
		||||
| 
						 | 
				
			
			@ -192,11 +257,22 @@ class NewContent implements \Federator\Api\APIInterface
 | 
			
		|||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // get recipient
 | 
			
		||||
        $recipient = \Federator\DIO\FedUser::getUserByName(
 | 
			
		||||
            $dbh,
 | 
			
		||||
            $_recipientId,
 | 
			
		||||
            $cache
 | 
			
		||||
        );
 | 
			
		||||
        if ($recipient === null || $recipient->id === null) {
 | 
			
		||||
            error_log("NewContent::postForUser couldn't find user: $_recipientId");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $rootDir = PROJECT_ROOT . '/';
 | 
			
		||||
        // Save the raw input and parsed JSON to a file for inspection
 | 
			
		||||
        file_put_contents(
 | 
			
		||||
            $rootDir . 'logs/newcontent_' . $_user . '.log',
 | 
			
		||||
            date('Y-m-d H:i:s') . ": ==== POST " . $_user . " NewContent Activity ====\n" . json_encode($newActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
 | 
			
		||||
            $rootDir . 'logs/newcontent_' . $recipient->id . '.log',
 | 
			
		||||
            date('Y-m-d H:i:s') . ": ==== POST " . $recipient->id . " NewContent Activity ====\n" . json_encode($newActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
 | 
			
		||||
            FILE_APPEND
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -303,6 +379,7 @@ class NewContent implements \Federator\Api\APIInterface
 | 
			
		|||
                break;
 | 
			
		||||
 | 
			
		||||
            case 'Create':
 | 
			
		||||
            case 'Update':
 | 
			
		||||
                $object = $newActivity->getObject();
 | 
			
		||||
                if (is_object($object)) {
 | 
			
		||||
                    switch ($object->getType()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -324,40 +401,6 @@ class NewContent implements \Federator\Api\APIInterface
 | 
			
		|||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * fetch all followers from url and return the ones that belong to our server
 | 
			
		||||
     *
 | 
			
		||||
     * @param \mysqli $dbh
 | 
			
		||||
     *          database handle
 | 
			
		||||
     * @param \Federator\Connector\Connector $connector
 | 
			
		||||
     *          connector to fetch use with
 | 
			
		||||
     * @param \Federator\Cache\Cache|null $cache
 | 
			
		||||
     *          optional caching service
 | 
			
		||||
     * @param string $userId The id of the user
 | 
			
		||||
     * @return string[] the names of the followers that are hosted on our server
 | 
			
		||||
     */
 | 
			
		||||
    private static function fetchAllFollowers($dbh, $connector, $cache, string $userId): array
 | 
			
		||||
    {
 | 
			
		||||
        if (empty($userId)) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $users = [];
 | 
			
		||||
 | 
			
		||||
        $apFollowers = \Federator\DIO\Followers::getFollowersByUser(
 | 
			
		||||
            $dbh,
 | 
			
		||||
            $userId,
 | 
			
		||||
            $connector,
 | 
			
		||||
            cache: $cache,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        foreach ($apFollowers as $follower) {
 | 
			
		||||
            $users[] = $follower->id;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $users;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * get internal represenation as json string
 | 
			
		||||
     * @return string json string or html
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										45
									
								
								php/federator/data/activitypub/common/Update.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								php/federator/data/activitypub/common/Update.php
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,45 @@
 | 
			
		|||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * SPDX-FileCopyrightText: 2024 Sascha Nitsch (grumpydeveloper) https://contentnation.net/@grumpydevelop
 | 
			
		||||
 * SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 *
 | 
			
		||||
 * @author Yannis Vogel (vogeldevelopment)
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
namespace Federator\Data\ActivityPub\Common;
 | 
			
		||||
 | 
			
		||||
class Update extends Activity
 | 
			
		||||
{
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct('Update');
 | 
			
		||||
        parent::addContext('https://www.w3.org/ns/activitystreams');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * convert internal state to php array
 | 
			
		||||
     *
 | 
			
		||||
     * @return array<string,mixed>
 | 
			
		||||
     */
 | 
			
		||||
    public function toObject()
 | 
			
		||||
    {
 | 
			
		||||
        $return = parent::toObject();
 | 
			
		||||
        $return['type'] = 'Update';
 | 
			
		||||
        // overwrite id from url
 | 
			
		||||
        if ($this->getURL() !== '') {
 | 
			
		||||
            $return['id'] = $this->getURL();
 | 
			
		||||
        }
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
   /**
 | 
			
		||||
     * create object from json
 | 
			
		||||
     *
 | 
			
		||||
     * @param array<string,mixed> $json input json
 | 
			
		||||
     * @return bool true on success
 | 
			
		||||
     */
 | 
			
		||||
    public function fromJson($json)
 | 
			
		||||
    {
 | 
			
		||||
        return parent::fromJson($json);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -647,7 +647,7 @@ class APObject implements \JsonSerializable
 | 
			
		|||
        if (array_key_exists('duration', $json)) {
 | 
			
		||||
            try {
 | 
			
		||||
                $this->duration = new \DateInterval($json['duration']);
 | 
			
		||||
            } catch (\Exception $unused_e) {
 | 
			
		||||
            } catch (\Throwable $unused_e) {
 | 
			
		||||
                error_log("error parsing duration ". $json['duration']);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -875,7 +875,7 @@ class APObject implements \JsonSerializable
 | 
			
		|||
            $return['tag'] = $tags;
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->updated > 0) {
 | 
			
		||||
            $return['updated'] = gmdate("Y-m-d\TH:i:S\Z", $this->updated);
 | 
			
		||||
            $return['updated'] = gmdate("Y-m-d\TH:i:s\Z", $this->updated);
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->url !== '') {
 | 
			
		||||
            $return['url'] = $this->url;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -116,6 +116,9 @@ class Factory
 | 
			
		|||
            case 'Undo':
 | 
			
		||||
                $return = new Common\Undo();
 | 
			
		||||
                break;
 | 
			
		||||
            case 'Update':
 | 
			
		||||
                $return = new Common\Update();
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                error_log("newActivityFromJson unsupported type: " . print_r($json, true));
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,7 +26,7 @@ class FedUser
 | 
			
		|||
        $sql = 'select unix_timestamp(`validuntil`) from fedusers where id=?';
 | 
			
		||||
        $stmt = $dbh->prepare($sql);
 | 
			
		||||
        if ($stmt === false) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("FedUser::addLocalUser Failed to prepare statement");
 | 
			
		||||
        }
 | 
			
		||||
        $stmt->bind_param("s", $_user);
 | 
			
		||||
        $validuntil = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +42,7 @@ class FedUser
 | 
			
		|||
            $sql .= ' values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, now() + interval 1 day)';
 | 
			
		||||
            $stmt = $dbh->prepare($sql);
 | 
			
		||||
            if ($stmt === false) {
 | 
			
		||||
                throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
                throw new \Federator\Exceptions\ServerError("FedUser::addLocalUser Failed to prepare create statement");
 | 
			
		||||
            }
 | 
			
		||||
            $stmt->bind_param(
 | 
			
		||||
                "ssssssssssss",
 | 
			
		||||
| 
						 | 
				
			
			@ -66,7 +66,7 @@ class FedUser
 | 
			
		|||
            $sql .= ' where id=?';
 | 
			
		||||
            $stmt = $dbh->prepare($sql);
 | 
			
		||||
            if ($stmt === false) {
 | 
			
		||||
                throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
                throw new \Federator\Exceptions\ServerError("FedUser::extendUser Failed to prepare update statement");
 | 
			
		||||
            }
 | 
			
		||||
            $stmt->bind_param(
 | 
			
		||||
                "ssssssssssss",
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +106,7 @@ class FedUser
 | 
			
		|||
        $sql = 'select id,unix_timestamp(`validuntil`) from fedusers where id=?';
 | 
			
		||||
        $stmt = $dbh->prepare($sql);
 | 
			
		||||
        if ($stmt === false) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("FedUser::extendUser Failed to prepare statement");
 | 
			
		||||
        }
 | 
			
		||||
        $stmt->bind_param("s", $_user);
 | 
			
		||||
        $validuntil = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -147,12 +147,12 @@ class FedUser
 | 
			
		|||
            return $user;
 | 
			
		||||
        }
 | 
			
		||||
        // check our db
 | 
			
		||||
        $sql = 'select id, url, name, publickey, summary, type, inboxurl, sharedinboxurl, followersurl,';
 | 
			
		||||
        $sql .= ' followingurl,publickeyid,outboxurl';
 | 
			
		||||
        $sql .= ' from fedusers where id=? and validuntil>=now()';
 | 
			
		||||
        $sql = 'select `id`, `url`, `name`, `publickey`, `summary`, `type`, `inboxurl`, `sharedinboxurl`, `followersurl`,';
 | 
			
		||||
        $sql .= ' `followingurl`, `publickeyid`, `outboxurl`';
 | 
			
		||||
        $sql .= ' from fedusers where `id`=? and `validuntil`>=now()';
 | 
			
		||||
        $stmt = $dbh->prepare($sql);
 | 
			
		||||
        if ($stmt === false) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to prepare statement");
 | 
			
		||||
        }
 | 
			
		||||
        $stmt->bind_param("s", $_name);
 | 
			
		||||
        $user = new \Federator\Data\FedUser();
 | 
			
		||||
| 
						 | 
				
			
			@ -184,11 +184,11 @@ class FedUser
 | 
			
		|||
                $headers = ['Accept: application/activity+json'];
 | 
			
		||||
                [$response, $info] = \Federator\Main::getFromRemote($remoteURL, $headers);
 | 
			
		||||
                if ($info['http_code'] != 200) {
 | 
			
		||||
                    throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
                    throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to fetch webfinger for " . $_name);
 | 
			
		||||
                }
 | 
			
		||||
                $r = json_decode($response, true);
 | 
			
		||||
                if ($r === false || $r === null || !is_array($r)) {
 | 
			
		||||
                    throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
                    throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to decode webfinger for " . $_name);
 | 
			
		||||
                }
 | 
			
		||||
                // get the webwinger user url and fetch the user
 | 
			
		||||
                if (isset($r['links'])) {
 | 
			
		||||
| 
						 | 
				
			
			@ -200,17 +200,17 @@ class FedUser
 | 
			
		|||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (!isset($remoteURL)) {
 | 
			
		||||
                    throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
                    throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to find self link in webfinger for " . $_name);
 | 
			
		||||
                }
 | 
			
		||||
                // fetch the user
 | 
			
		||||
                $headers = ['Accept: application/activity+json'];
 | 
			
		||||
                [$response, $info] = \Federator\Main::getFromRemote($remoteURL, $headers);
 | 
			
		||||
                if ($info['http_code'] != 200) {
 | 
			
		||||
                    throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
                    throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to fetch user from remoteUrl for " . $_name);
 | 
			
		||||
                }
 | 
			
		||||
                $r = json_decode($response, true);
 | 
			
		||||
                if ($r === false || $r === null || !is_array($r)) {
 | 
			
		||||
                    throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
                    throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to decode user for " . $_name);
 | 
			
		||||
                }
 | 
			
		||||
                $r['publicKeyId'] = $r['publicKey']['id'];
 | 
			
		||||
                $r['publicKey'] = $r['publicKey']['publicKeyPem'];
 | 
			
		||||
| 
						 | 
				
			
			@ -222,20 +222,20 @@ class FedUser
 | 
			
		|||
                $r['actorURL'] = $remoteURL;
 | 
			
		||||
                $data = json_encode($r);
 | 
			
		||||
                if ($data === false) {
 | 
			
		||||
                    throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
                    throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to encode userdata " . $_name);
 | 
			
		||||
                }
 | 
			
		||||
                $user = \Federator\Data\FedUser::createFromJson($data);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($cache !== null && $user !== false) {
 | 
			
		||||
            if ($user->id === null && $user->actorURL !== null) {
 | 
			
		||||
            if ($user->id !== null && $user->actorURL !== null) {
 | 
			
		||||
                self::addLocalUser($dbh, $user, $_name);
 | 
			
		||||
            }
 | 
			
		||||
            $cache->saveRemoteFedUserByName($_name, $user);
 | 
			
		||||
        }
 | 
			
		||||
        if ($user === false) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("FedUser::getUserByName User not found");
 | 
			
		||||
        }
 | 
			
		||||
        return $user;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,7 +39,7 @@ class Followers
 | 
			
		|||
        $sql = 'select source_user from follows where target_user = ?';
 | 
			
		||||
        $stmt = $dbh->prepare($sql);
 | 
			
		||||
        if ($stmt === false) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("Followers::getFollowersByUser Failed to prepare statement");
 | 
			
		||||
        }
 | 
			
		||||
        $stmt->bind_param("s", $id);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
| 
						 | 
				
			
			@ -50,11 +50,16 @@ class Followers
 | 
			
		|||
        }
 | 
			
		||||
        $stmt->close();
 | 
			
		||||
        foreach ($followerIds as $followerId) {
 | 
			
		||||
            $user = \Federator\DIO\FedUser::getUserByName(
 | 
			
		||||
                $dbh,
 | 
			
		||||
                $followerId,
 | 
			
		||||
                $cache,
 | 
			
		||||
            );
 | 
			
		||||
            try {
 | 
			
		||||
                $user = \Federator\DIO\FedUser::getUserByName(
 | 
			
		||||
                    $dbh,
 | 
			
		||||
                    $followerId,
 | 
			
		||||
                    $cache,
 | 
			
		||||
                );
 | 
			
		||||
            } catch (\Throwable $e) {
 | 
			
		||||
                error_log("Followers::getFollowersByUser Exception: " . $e->getMessage());
 | 
			
		||||
                continue; // Skip this user if an exception occurs
 | 
			
		||||
            }
 | 
			
		||||
            if ($user !== false && $user->id !== null) {
 | 
			
		||||
                $followers[] = $user;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -67,7 +72,7 @@ class Followers
 | 
			
		|||
                $followers = [];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // save posts to DB
 | 
			
		||||
        // save followers to cache
 | 
			
		||||
        if ($cache !== null) {
 | 
			
		||||
            $cache->saveRemoteFollowersOfUser($id, $followers);
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -100,7 +105,7 @@ class Followers
 | 
			
		|||
        $sql = 'select target_user from follows where source_user = ?';
 | 
			
		||||
        $stmt = $dbh->prepare($sql);
 | 
			
		||||
        if ($stmt === false) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("Followers::getFollowingForUser Failed to prepare statement");
 | 
			
		||||
        }
 | 
			
		||||
        $stmt->bind_param("s", $id);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
| 
						 | 
				
			
			@ -111,11 +116,16 @@ class Followers
 | 
			
		|||
        }
 | 
			
		||||
        $stmt->close();
 | 
			
		||||
        foreach ($followingIds as $followingId) {
 | 
			
		||||
            $user = \Federator\DIO\FedUser::getUserByName(
 | 
			
		||||
                $dbh,
 | 
			
		||||
                $followingId,
 | 
			
		||||
                $cache,
 | 
			
		||||
            );
 | 
			
		||||
            try {
 | 
			
		||||
                $user = \Federator\DIO\FedUser::getUserByName(
 | 
			
		||||
                    $dbh,
 | 
			
		||||
                    $followingId,
 | 
			
		||||
                    $cache,
 | 
			
		||||
                );
 | 
			
		||||
            } catch (\Throwable $e) {
 | 
			
		||||
                error_log("Followers::getFollowingForUser Exception: " . $e->getMessage());
 | 
			
		||||
                continue; // Skip this user if an exception occurs
 | 
			
		||||
            }
 | 
			
		||||
            if ($user !== false && $user->id !== null) {
 | 
			
		||||
                $following[] = $user;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -156,7 +166,7 @@ class Followers
 | 
			
		|||
        $sql = 'select source_user from follows where target_user = ?';
 | 
			
		||||
        $stmt = $dbh->prepare($sql);
 | 
			
		||||
        if ($stmt === false) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("Followers::getFollowersByFedUser Failed to prepare statement");
 | 
			
		||||
        }
 | 
			
		||||
        $stmt->bind_param("s", $id);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
| 
						 | 
				
			
			@ -166,12 +176,17 @@ class Followers
 | 
			
		|||
            $followerIds[] = $sourceUser;
 | 
			
		||||
        }
 | 
			
		||||
        foreach ($followerIds as $followerId) {
 | 
			
		||||
            $user = \Federator\DIO\User::getUserByName(
 | 
			
		||||
                $dbh,
 | 
			
		||||
                $followerId,
 | 
			
		||||
                $connector,
 | 
			
		||||
                $cache
 | 
			
		||||
            );
 | 
			
		||||
            try {
 | 
			
		||||
                $user = \Federator\DIO\User::getUserByName(
 | 
			
		||||
                    $dbh,
 | 
			
		||||
                    $followerId,
 | 
			
		||||
                    $connector,
 | 
			
		||||
                    $cache
 | 
			
		||||
                );
 | 
			
		||||
            } catch (\Throwable $e) {
 | 
			
		||||
                error_log("Followers::getFollowersByFedUser Exception: " . $e->getMessage());
 | 
			
		||||
                continue; // Skip this user if an exception occurs
 | 
			
		||||
            }
 | 
			
		||||
            if ($user !== false && $user->id !== null) {
 | 
			
		||||
                $followers[] = $user;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -193,7 +208,7 @@ class Followers
 | 
			
		|||
    public static function sendFollowRequest($dbh, $connector, $cache, $_user, $_targetUser, $host)
 | 
			
		||||
    {
 | 
			
		||||
        if ($dbh === false) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("Followers::sendFollowRequest Failed to get database handle");
 | 
			
		||||
        }
 | 
			
		||||
        $user = \Federator\DIO\User::getUserByName(
 | 
			
		||||
            $dbh,
 | 
			
		||||
| 
						 | 
				
			
			@ -326,7 +341,7 @@ class Followers
 | 
			
		|||
        $sql = 'select id from follows where source_user = ? and target_user = ?';
 | 
			
		||||
        $stmt = $dbh->prepare($sql);
 | 
			
		||||
        if ($stmt === false) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("Followers::addFollow Failed to prepare statement");
 | 
			
		||||
        }
 | 
			
		||||
        $stmt->bind_param("ss", $sourceUser, $targetUserId);
 | 
			
		||||
        $foundId = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -349,7 +364,7 @@ class Followers
 | 
			
		|||
            $sql = 'select id from follows where id = ?';
 | 
			
		||||
            $stmt = $dbh->prepare($sql);
 | 
			
		||||
            if ($stmt === false) {
 | 
			
		||||
                throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
                throw new \Federator\Exceptions\ServerError("Followers::addFollow Failed to prepare id-check statement");
 | 
			
		||||
            }
 | 
			
		||||
            $stmt->bind_param("s", $idurl);
 | 
			
		||||
            $foundId = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -365,7 +380,7 @@ class Followers
 | 
			
		|||
        $sql = 'insert into follows (id, source_user, target_user, created_at) values (?, ?, ?, NOW())';
 | 
			
		||||
        $stmt = $dbh->prepare($sql);
 | 
			
		||||
        if ($stmt === false) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("Followers::addFollow Failed to prepare insert statement");
 | 
			
		||||
        }
 | 
			
		||||
        $stmt->bind_param("sss", $idurl, $sourceUser, $targetUserId);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
| 
						 | 
				
			
			@ -386,7 +401,7 @@ class Followers
 | 
			
		|||
        $sql = 'delete from follows where source_user = ? and target_user = ?';
 | 
			
		||||
        $stmt = $dbh->prepare($sql);
 | 
			
		||||
        if ($stmt === false) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("Followers::removeFollow Failed to prepare statement");
 | 
			
		||||
        }
 | 
			
		||||
        $stmt->bind_param("ss", $sourceUser, $targetUserId);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,7 +43,6 @@ 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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,7 +26,7 @@ class User
 | 
			
		|||
        $sql = 'select unix_timestamp(`validuntil`) from users where id=?';
 | 
			
		||||
        $stmt = $dbh->prepare($sql);
 | 
			
		||||
        if ($stmt === false) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("User::addLocalUser Failed to prepare statement");
 | 
			
		||||
        }
 | 
			
		||||
        $stmt->bind_param("s", $_user);
 | 
			
		||||
        $validuntil = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -50,7 +50,7 @@ class User
 | 
			
		|||
            $sql .= ' values (?, ?, ?, ?, now() + interval 1 day, ?, ?, ?, ?, ?, ?, ?, ?)';
 | 
			
		||||
            $stmt = $dbh->prepare($sql);
 | 
			
		||||
            if ($stmt === false) {
 | 
			
		||||
                throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
                throw new \Federator\Exceptions\ServerError("User::addLocalUser Failed to prepare create statement");
 | 
			
		||||
            }
 | 
			
		||||
            $registered = gmdate('Y-m-d H:i:s', $user->registered);
 | 
			
		||||
            $stmt->bind_param(
 | 
			
		||||
| 
						 | 
				
			
			@ -74,7 +74,7 @@ class User
 | 
			
		|||
            $sql .= ' iconmediatype=?, iconurl=?, imagemediatype=?, imageurl=? where id=?';
 | 
			
		||||
            $stmt = $dbh->prepare($sql);
 | 
			
		||||
            if ($stmt === false) {
 | 
			
		||||
                throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
                throw new \Federator\Exceptions\ServerError("User::addLocalUser Failed to prepare update statement");
 | 
			
		||||
            }
 | 
			
		||||
            $registered = gmdate('Y-m-d H:i:s', $user->registered);
 | 
			
		||||
            $stmt->bind_param(
 | 
			
		||||
| 
						 | 
				
			
			@ -110,7 +110,7 @@ class User
 | 
			
		|||
        $sql = "select rsaprivate from users where id=?";
 | 
			
		||||
        $stmt = $dbh->prepare($sql);
 | 
			
		||||
        if ($stmt === false) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("User::getrsaprivate Failed to prepare statement");
 | 
			
		||||
        }
 | 
			
		||||
        $stmt->bind_param("s", $_user);
 | 
			
		||||
        $ret = $stmt->bind_result($rsaPrivateKey);
 | 
			
		||||
| 
						 | 
				
			
			@ -136,7 +136,7 @@ class User
 | 
			
		|||
        $sql = 'select id,unix_timestamp(`validuntil`) from users where id=?';
 | 
			
		||||
        $stmt = $dbh->prepare($sql);
 | 
			
		||||
        if ($stmt === false) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("User::extendUser Failed to prepare statement");
 | 
			
		||||
        }
 | 
			
		||||
        $stmt->bind_param("s", $_user);
 | 
			
		||||
        $validuntil = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -183,7 +183,7 @@ class User
 | 
			
		|||
        $sql .= 'iconmediatype,iconurl,imagemediatype,imageurl from users where id=? and validuntil>=now()';
 | 
			
		||||
        $stmt = $dbh->prepare($sql);
 | 
			
		||||
        if ($stmt === false) {
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError();
 | 
			
		||||
            throw new \Federator\Exceptions\ServerError("User::getUserByName Failed to prepare statement");
 | 
			
		||||
        }
 | 
			
		||||
        $stmt->bind_param("s", $_name);
 | 
			
		||||
        $user = new \Federator\Data\User();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,6 +54,7 @@ class InboxJob extends \Federator\Api
 | 
			
		|||
    {
 | 
			
		||||
        error_log("InboxJob: Starting inbox job");
 | 
			
		||||
        $user = $this->args['user'];
 | 
			
		||||
        $recipientId = $this->args['recipientId'];
 | 
			
		||||
        $activity = $this->args['activity'];
 | 
			
		||||
 | 
			
		||||
        $inboxActivity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($activity);
 | 
			
		||||
| 
						 | 
				
			
			@ -63,7 +64,7 @@ class InboxJob extends \Federator\Api
 | 
			
		|||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        \Federator\Api\FedUsers\Inbox::postForUser($this->dbh, $this->connector, $this->cache, $user, $inboxActivity);
 | 
			
		||||
        \Federator\Api\FedUsers\Inbox::postForUser($this->dbh, $this->connector, $this->cache, $user, $recipientId, $inboxActivity);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -54,6 +54,7 @@ class NewContentJob extends \Federator\Api
 | 
			
		|||
    {
 | 
			
		||||
        error_log("NewContentJob: Starting inbox job");
 | 
			
		||||
        $user = $this->args['user'];
 | 
			
		||||
        $recipientId = $this->args['recipientId'];
 | 
			
		||||
        $activity = $this->args['activity'];
 | 
			
		||||
 | 
			
		||||
        $activity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($activity);
 | 
			
		||||
| 
						 | 
				
			
			@ -63,7 +64,7 @@ class NewContentJob extends \Federator\Api
 | 
			
		|||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        \Federator\Api\V1\NewContent::postForUser($this->dbh, $this->connector, $this->cache, $user, $activity);
 | 
			
		||||
        \Federator\Api\V1\NewContent::postForUser($this->dbh, $this->connector, $this->cache, $user, $recipientId, $activity);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -148,8 +148,8 @@ class ContentNation implements Connector
 | 
			
		|||
                        $create->setAActor('https://' . $domain . '/' . $userId);
 | 
			
		||||
                        $create->setID($activity['id'])
 | 
			
		||||
                            ->setPublished($activity['published'] ?? $activity['timestamp'])
 | 
			
		||||
                            ->addTo("https://www.w3.org/ns/activitystreams#Public")
 | 
			
		||||
                            ->addCC('https://' . $domain . '/' . $userId . '/followers');
 | 
			
		||||
                            ->addTo('https://' . $domain . '/' . $userId . '/followers')
 | 
			
		||||
                            ->addCC("https://www.w3.org/ns/activitystreams#Public");
 | 
			
		||||
                        $create->setURL('https://' . $domain . '/' . $activity['profilename'] . '/' . $activity['name']);
 | 
			
		||||
                        $create->setID('https://' . $domain . '/' . $activity['profilename'] . '/' . $activity['id']);
 | 
			
		||||
                        $apArticle = new \Federator\Data\ActivityPub\Common\Article();
 | 
			
		||||
| 
						 | 
				
			
			@ -203,8 +203,8 @@ class ContentNation implements Connector
 | 
			
		|||
                        $create->setAActor('https://' . $domain . '/' . $userId);
 | 
			
		||||
                        $create->setID($activity['id'])
 | 
			
		||||
                            ->setPublished($activity['published'] ?? $activity['timestamp'])
 | 
			
		||||
                            ->addTo("https://www.w3.org/ns/activitystreams#Public")
 | 
			
		||||
                            ->addCC('https://' . $domain . '/' . $userId . '/followers');
 | 
			
		||||
                            ->addTo('https://' . $domain . '/' . $userId . '/followers')
 | 
			
		||||
                            ->addCC("https://www.w3.org/ns/activitystreams#Public");
 | 
			
		||||
                        $commentJson = $activity;
 | 
			
		||||
                        $commentJson['type'] = 'Note';
 | 
			
		||||
                        $commentJson['summary'] = $activity['subject'];
 | 
			
		||||
| 
						 | 
				
			
			@ -219,7 +219,7 @@ class ContentNation implements Connector
 | 
			
		|||
                        $note->setID($commentJson['id']);
 | 
			
		||||
                        if (!isset($commentJson['parent']) || $commentJson['parent'] === null) {
 | 
			
		||||
                            $note->setInReplyTo('https://' . $domain . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName']);
 | 
			
		||||
                        } elseif ($replyType === "comment") {
 | 
			
		||||
                        } else {
 | 
			
		||||
                            $note->setInReplyTo('https://' . $domain . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName'] . "#" . $commentJson['parent']);
 | 
			
		||||
                        }
 | 
			
		||||
                        $url = 'https://' . $domain . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName'] . '#' . $activity['id'];
 | 
			
		||||
| 
						 | 
				
			
			@ -237,7 +237,7 @@ class ContentNation implements Connector
 | 
			
		|||
                        $like->setID($activity['id'])
 | 
			
		||||
                            ->setPublished($activity['published'] ?? $activity['timestamp']);
 | 
			
		||||
                        // $like->addTo("https://www.w3.org/ns/activitystreams#Public")
 | 
			
		||||
                            // ->addCC('https://' . $domain . '/' . $userId . '/followers');
 | 
			
		||||
                        // ->addCC('https://' . $domain . '/' . $userId . '/followers');
 | 
			
		||||
                        $like->setSummary(
 | 
			
		||||
                            $this->main->translate(
 | 
			
		||||
                                $activity['articlelang'],
 | 
			
		||||
| 
						 | 
				
			
			@ -392,6 +392,65 @@ class ContentNation implements Connector
 | 
			
		|||
 | 
			
		||||
        // Handle specific fields based on the type
 | 
			
		||||
        switch ($jsonData['type']) {
 | 
			
		||||
            case 'article':
 | 
			
		||||
                $articleName = $jsonData['object']['name'] ?? null;
 | 
			
		||||
                $articleOwnerName = $jsonData['object']['ownerName'] ?? null;
 | 
			
		||||
                // Set Create-level fields
 | 
			
		||||
                $updatedOn = $jsonData['object']['modified'] ?? null;
 | 
			
		||||
                $originalPublished = $jsonData['object']['published'] ?? null;
 | 
			
		||||
                $update = $updatedOn !== $originalPublished;
 | 
			
		||||
                $ap['published'] = $updatedOn ?? $originalPublished;
 | 
			
		||||
                $ap['id'] = $ourUrl . "/" . $articleOwnerName . "/" . $articleName;
 | 
			
		||||
                $ap['url'] = $ourUrl . "/" . $articleOwnerName . "/" . $articleName;
 | 
			
		||||
                $ap['type'] = $update ? 'Update' : 'Create';
 | 
			
		||||
                $ap['actor'] = $ourUrl . '/' . $actorName;
 | 
			
		||||
                // Set Article-level fields
 | 
			
		||||
                $ap['object'] = [
 | 
			
		||||
                    'type' => 'Article',
 | 
			
		||||
                    'id' => $ourUrl . "/" . $articleOwnerName . "/" . $articleName,
 | 
			
		||||
                    'name' => $jsonData['object']['name'] ?? null,
 | 
			
		||||
                    'published' => $originalPublished,
 | 
			
		||||
                    'summary' => $jsonData['object']['summary'] ?? null,
 | 
			
		||||
                    'content' => $jsonData['object']['content'] ?? null,
 | 
			
		||||
                    'attributedTo' => $ap['actor'],
 | 
			
		||||
                    'url' => $ap['url'],
 | 
			
		||||
                    'cc' => ['https://www.w3.org/ns/activitystreams#Public'],
 | 
			
		||||
                ];
 | 
			
		||||
                if ($update) {
 | 
			
		||||
                    $ap['id'] .= '#update';
 | 
			
		||||
                    $ap['url'] .= '#update';
 | 
			
		||||
                    $ap['object']['updated'] = $updatedOn;
 | 
			
		||||
                }
 | 
			
		||||
                $ap['cc'] = ['https://www.w3.org/ns/activitystreams#Public'];
 | 
			
		||||
                if (isset($jsonData['object']['tags'])) {
 | 
			
		||||
                    if (is_array($jsonData['object']['tags'])) {
 | 
			
		||||
                        foreach ($jsonData['object']['tags'] as $tag) {
 | 
			
		||||
                            $ap['object']['tags'][] = $tag;
 | 
			
		||||
                        }
 | 
			
		||||
                    } elseif (is_string($jsonData['object']['tags']) && $jsonData['object']['tags'] !== '') {
 | 
			
		||||
                        // If it's a single tag as a string, add it as a one-element array
 | 
			
		||||
                        $ap['object']['tags'][] = $jsonData['object']['tags'];
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (isset($jsonData['options'])) {
 | 
			
		||||
                    if (isset($jsonData['options']['informFollowers'])) {
 | 
			
		||||
                        if ($jsonData['options']['informFollowers'] === true) {
 | 
			
		||||
                            $ap['to'][] = $ourUrl . '/' . $actorName . '/followers';
 | 
			
		||||
                            $ap['object']['to'][] = $ourUrl . '/' . $actorName . '/followers';
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                $returnActivity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($ap);
 | 
			
		||||
                if ($returnActivity === false) {
 | 
			
		||||
                    error_log("ContentNation::jsonToActivity couldn't create article");
 | 
			
		||||
                    $returnActivity = new \Federator\Data\ActivityPub\Common\Activity('Create');
 | 
			
		||||
                } else {
 | 
			
		||||
                    $returnActivity->setID($ap['id']);
 | 
			
		||||
                    $returnActivity->setURL($ap['url']);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case 'comment':
 | 
			
		||||
                $commentId = $jsonData['object']['id'] ?? null;
 | 
			
		||||
                $articleName = $jsonData['object']['articleName'] ?? null;
 | 
			
		||||
| 
						 | 
				
			
			@ -411,7 +470,7 @@ class ContentNation implements Connector
 | 
			
		|||
                if (isset($jsonData['options'])) {
 | 
			
		||||
                    if (isset($jsonData['options']['informFollowers'])) {
 | 
			
		||||
                        if ($jsonData['options']['informFollowers'] === true) {
 | 
			
		||||
                            if ($actorName != $articleOwnerName) {
 | 
			
		||||
                            if ($actorName !== $articleOwnerName) {
 | 
			
		||||
                                $ap['to'][] = $ourUrl . '/' . $articleOwnerName;
 | 
			
		||||
                            }
 | 
			
		||||
                            $ap['to'][] = $ourUrl . '/' . $actorName . '/followers';
 | 
			
		||||
| 
						 | 
				
			
			@ -427,8 +486,13 @@ class ContentNation implements Connector
 | 
			
		|||
                    error_log("ContentNation::jsonToActivity unknown inReplyTo type: {$replyType}");
 | 
			
		||||
                }
 | 
			
		||||
                $returnActivity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($ap);
 | 
			
		||||
                $returnActivity->setID($ap['id']);
 | 
			
		||||
                $returnActivity->setURL($ap['url']);
 | 
			
		||||
                if ($returnActivity === false) {
 | 
			
		||||
                    error_log("ContentNation::jsonToActivity couldn't create comment");
 | 
			
		||||
                    $returnActivity = new \Federator\Data\ActivityPub\Common\Activity('Create');
 | 
			
		||||
                } else {
 | 
			
		||||
                    $returnActivity->setID($ap['id']);
 | 
			
		||||
                    $returnActivity->setURL($ap['url']);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case 'vote':
 | 
			
		||||
| 
						 | 
				
			
			@ -487,8 +551,19 @@ class ContentNation implements Connector
 | 
			
		|||
                } */
 | 
			
		||||
 | 
			
		||||
                $returnActivity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($ap);
 | 
			
		||||
                $returnActivity->setID($ap['id']);
 | 
			
		||||
                $returnActivity->setURL($ap['url']);
 | 
			
		||||
                if ($returnActivity === false) {
 | 
			
		||||
                    error_log("ContentNation::jsonToActivity couldn't create vote");
 | 
			
		||||
                    if ($ap['type'] === "Like") {
 | 
			
		||||
                        $returnActivity = new \Federator\Data\ActivityPub\Common\Like();
 | 
			
		||||
                    } elseif ($ap['type'] === "Dislike") {
 | 
			
		||||
                        $returnActivity = new \Federator\Data\ActivityPub\Common\Dislike();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        $returnActivity = new \Federator\Data\ActivityPub\Common\Undo();
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    $returnActivity->setID($ap['id']);
 | 
			
		||||
                    $returnActivity->setURL($ap['url']);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            default:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue