Add Remote Flow (#43) and PHP Unit Tests from dansup (#37)

Now the code is following most of PSR
Various changes from various branches (some testing will be required)
Fixed various issues
This commit is contained in:
Diogo Cordeiro 2018-07-26 21:12:13 +00:00
parent e377b87ff7
commit eaad9423dd
45 changed files with 4723 additions and 1918 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/vendor/

File diff suppressed because it is too large Load Diff

View File

@ -6,11 +6,10 @@ email, or any other method with the owners of this repository before making a ch
Please note we have a code of conduct, please follow it in all your interactions with the project.
# Coding Style
- We are using [K&R Variant: Linux kernel](https://en.wikipedia.org/wiki/Indentation_style#Variant:_Linux_kernel) with spaces before every `(`.
- Every function has a docblock explaining what it does and stating the author, parameters, types, return and exceptions
- We use snake_case
- We follow every [PSR-2](https://www.php-fig.org/psr/psr-2/) ...
- ... except camelCase, that's too bad, we use snake_case
## Pull Request Process
## Merge Request Process
1. Ensure you strip any trailing spaces off
2. Increase the version numbers in any examples files and the README.md to the new version that this

View File

@ -67,4 +67,3 @@ License along with this program, in the file "COPYING". If not, see
to your users under the same license. This is a legal requirement
of using the software, and if you do not wish to share your
modifications, *YOU MAY NOT USE THIS PLUGIN*.

View File

@ -25,8 +25,8 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
@ -40,64 +40,62 @@ if (!defined ('GNUSOCIAL')) {
*/
class apActorFollowersAction extends ManagedAction
{
protected $needLogin = false;
protected $canPost = true;
protected $needLogin = false;
protected $canPost = true;
/**
* Handle the Followers Collection request
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return void
*/
protected function handle ()
{
$nickname = $this->trimmed ('nickname');
try {
$user = User::getByNickname ($nickname);
$profile = $user->getProfile ();
$url = $profile->profileurl;
} catch (Exception $e) {
ActivityPubReturn::error ('Invalid username.');
}
/**
* Handle the Followers Collection request
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return void
*/
protected function handle()
{
try {
$profile = Profile::getByID($this->trimmed('id'));
$url = ActivityPubPlugin::actor_url($profile);
} catch (Exception $e) {
ActivityPubReturn::error('Invalid Actor URI.', 404);
}
if (!isset ($_GET["page"])) {
$page = 1;
} else {
$page = intval ($this->trimmed ('page'));
}
if (!isset($_GET["page"])) {
$page = 1;
} else {
$page = intval($this->trimmed('page'));
}
if ($page <= 0) {
ActivityPubReturn::error ('Invalid page number.');
}
if ($page <= 0) {
ActivityPubReturn::error('Invalid page number.');
}
/* Fetch Followers */
try {
$since = ($page - 1) * PROFILES_PER_MINILIST;
$limit = (($page - 1) == 0 ? 1 : $page) * PROFILES_PER_MINILIST;
$sub = $profile->getSubscribers ($since, $limit);
} catch (NoResultException $e) {
ActivityPubReturn::error ('This user has no followers.');
}
/* Fetch Followers */
try {
$since = ($page - 1) * PROFILES_PER_MINILIST;
$limit = (($page - 1) == 0 ? 1 : $page) * PROFILES_PER_MINILIST;
$sub = $profile->getSubscribers($since, $limit);
} catch (NoResultException $e) {
ActivityPubReturn::error('This user has no followers.');
}
/* Calculate total items */
$total_subs = $profile->subscriberCount ();
$total_pages = ceil ($total_subs / PROFILES_PER_MINILIST);
/* Calculate total items */
$total_subs = $profile->subscriberCount();
$total_pages = ceil($total_subs / PROFILES_PER_MINILIST);
if ($total_pages == 0) {
ActivityPubReturn::error ('This user has no followers.');
}
if ($total_pages == 0) {
ActivityPubReturn::error('This user has no followers.');
}
if ($page > $total_pages) {
ActivityPubReturn::error ("There are only {$total_pages} pages.");
}
if ($page > $total_pages) {
ActivityPubReturn::error("There are only {$total_pages} pages.");
}
/* Get followers' URLs */
$subs = array ();
while ($sub->fetch ()) {
$subs[] = $sub->profileurl;
}
/* Get followers' URLs */
$subs = array();
while ($sub->fetch()) {
$subs[] = $sub->profileurl;
}
$res = [
$res = [
'@context' => [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
@ -110,6 +108,6 @@ class apActorFollowersAction extends ManagedAction
'orderedItems' => $subs
];
ActivityPubReturn::answer ($res);
}
ActivityPubReturn::answer($res);
}
}

View File

@ -25,8 +25,8 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
@ -40,64 +40,62 @@ if (!defined ('GNUSOCIAL')) {
*/
class apActorFollowingAction extends ManagedAction
{
protected $needLogin = false;
protected $canPost = true;
protected $needLogin = false;
protected $canPost = true;
/**
* Handle the Following Collection request
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return void
*/
protected function handle ()
{
$nickname = $this->trimmed ('nickname');
try {
$user = User::getByNickname ($nickname);
$profile = $user->getProfile ();
$url = $profile->profileurl;
} catch (Exception $e) {
ActivityPubReturn::error ('Invalid username.');
}
/**
* Handle the Following Collection request
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return void
*/
protected function handle()
{
try {
$profile = Profile::getByID($this->trimmed('id'));
$url = ActivityPubPlugin::actor_url($profile);
} catch (Exception $e) {
ActivityPubReturn::error('Invalid Actor URI.', 404);
}
if (!isset ($_GET["page"])) {
$page = 1;
} else {
$page = intval ($this->trimmed ('page'));
}
if (!isset($_GET["page"])) {
$page = 1;
} else {
$page = intval($this->trimmed('page'));
}
if ($page <= 0) {
ActivityPubReturn::error ('Invalid page number.');
}
if ($page <= 0) {
ActivityPubReturn::error('Invalid page number.');
}
/* Fetch Following */
try {
$since = ($page - 1) * PROFILES_PER_MINILIST;
$limit = (($page - 1) == 0 ? 1 : $page) * PROFILES_PER_MINILIST;
$sub = $profile->getSubscribed ($since, $limit);
} catch (NoResultException $e) {
ActivityPubReturn::error ('This user is not following anyone.');
}
/* Fetch Following */
try {
$since = ($page - 1) * PROFILES_PER_MINILIST;
$limit = (($page - 1) == 0 ? 1 : $page) * PROFILES_PER_MINILIST;
$sub = $profile->getSubscribed($since, $limit);
} catch (NoResultException $e) {
ActivityPubReturn::error('This user is not following anyone.');
}
/* Calculate total items */
$total_subs = $profile->subscriptionCount();
$total_pages = ceil ($total_subs / PROFILES_PER_MINILIST);
/* Calculate total items */
$total_subs = $profile->subscriptionCount();
$total_pages = ceil($total_subs / PROFILES_PER_MINILIST);
if ($total_pages == 0) {
ActivityPubReturn::error ('This user is not following anyone.');
}
if ($total_pages == 0) {
ActivityPubReturn::error('This user is not following anyone.');
}
if ($page > $total_pages) {
ActivityPubReturn::error ("There are only {$total_pages} pages.");
}
if ($page > $total_pages) {
ActivityPubReturn::error("There are only {$total_pages} pages.");
}
/* Get followed' URLs */
$subs = array ();
while ($sub->fetch ()) {
$subs[] = $sub->profileurl;
}
/* Get followed' URLs */
$subs = array();
while ($sub->fetch()) {
$subs[] = $sub->profileurl;
}
$res = [
$res = [
'@context' => [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
@ -110,6 +108,6 @@ class apActorFollowingAction extends ManagedAction
'orderedItems' => $subs
];
ActivityPubReturn::answer ($res);
}
ActivityPubReturn::answer($res);
}
}

View File

@ -25,8 +25,8 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
@ -40,57 +40,59 @@ if (!defined ('GNUSOCIAL')) {
*/
class apActorInboxAction extends ManagedAction
{
protected $needLogin = false;
protected $canPost = true;
protected $needLogin = false;
protected $canPost = true;
/**
* Handle the Actor Inbox request
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return void
*/
protected function handle ()
{
$nickname = $this->trimmed ('nickname');
try {
$user = User::getByNickname ($nickname);
$profile = $user->getProfile ();
$url = $profile->profileurl;
} catch (Exception $e) {
ActivityPubReturn::error ("Invalid username.");
}
/**
* Handle the Actor Inbox request
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return void
*/
protected function handle()
{
try {
$profile = Profile::getByID($this->trimmed('id'));
} catch (Exception $e) {
ActivityPubReturn::error('Invalid Actor URI.', 404);
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
ActivityPubReturn::error ("C2S not implemented just yet.");
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
ActivityPubReturn::error("C2S not implemented just yet.");
}
$data = json_decode (file_get_contents ('php://input'));
$data = json_decode(file_get_contents('php://input'));
// Validate data
if (!(isset ($data->type))) {
ActivityPubReturn::error ("Type was not specified.");
}
if (!isset ($data->actor)) {
ActivityPubReturn::error ("Actor was not specified.");
}
if (!isset ($data->object)) {
ActivityPubReturn::error ("Object was not specified.");
}
// Validate data
if (!(isset($data->type))) {
ActivityPubReturn::error("Type was not specified.");
}
if (!isset($data->actor)) {
ActivityPubReturn::error("Actor was not specified.");
}
if (!isset($data->object)) {
ActivityPubReturn::error("Object was not specified.");
}
// Get valid Actor object
try {
require_once dirname (__DIR__) . DIRECTORY_SEPARATOR . "utils" . DIRECTORY_SEPARATOR . "explorer.php";
$actor_profile = new Activitypub_explorer;
$actor_profile = $actor_profile->lookup ($data->actor);
$actor_profile = $actor_profile[0];
} catch (Exception $e) {
ActivityPubReturn::error ("Invalid Actor.", 404);
}
// Get valid Actor object
try {
require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . "utils" . DIRECTORY_SEPARATOR . "explorer.php";
$actor_profile = new Activitypub_explorer;
$actor_profile = $actor_profile->lookup($data->actor);
$actor_profile = $actor_profile[0];
} catch (Exception $e) {
ActivityPubReturn::error("Invalid Actor.", 404);
}
$to_profiles = array ($user);
// Public To:
$public_to = array("https://www.w3.org/ns/activitystreams#Public",
"Public",
"as:Public");
// Process request
switch ($data->type) {
$to_profiles = array($profile);
// Process request
switch ($data->type) {
case "Create":
require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Create.php";
break;
@ -109,8 +111,14 @@ class apActorInboxAction extends ManagedAction
case "Announce":
require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Announce.php";
break;
case "Accept":
require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Accept.php";
break;
case "Reject":
require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Reject.php";
break;
default:
ActivityPubReturn::error ("Invalid type value.");
ActivityPubReturn::error("Invalid type value.");
}
}
}
}

152
actions/apactorliked.php Normal file
View File

@ -0,0 +1,152 @@
<?php
/**
* GNU social - a federating social network
*
* ActivityPubPlugin implementation for GNU Social
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
* Actor's Liked Collection
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://www.gnu.org/software/social/
*/
class apActorLikedAction extends ManagedAction
{
protected $needLogin = false;
protected $canPost = true;
/**
* Handle the Liked Collection request
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return void
*/
protected function handle()
{
$nickname = $this->trimmed('nickname');
try {
$user = User::getByNickname($nickname);
$profile = $user->getProfile();
$url = $profile->profileurl;
} catch (Exception $e) {
ActivityPubReturn::error('Invalid username.');
}
$limit = intval($this->trimmed('limit'));
$since_id = intval($this->trimmed('since_id'));
$max_id = intval($this->trimmed('max_id'));
$limit = empty($limit) ? 40 : $limit; // Default is 40
$since_id = empty($since_id) ? null : $since_id;
$max_id = empty($max_id) ? null : $max_id;
// Max is 80
if ($limit > 80) {
$limit = 80;
}
$fave = $this->fetch_faves($user->getID(), $limit, $since_id, $max_id);
$faves = array();
while ($fave->fetch()) {
$faves[] = $this->pretty_fave(clone ($fave));
}
$res = [
'@context' => [
"https://www.w3.org/ns/activitystreams",
[
"@language" => "en"
]
],
'id' => "{$url}/liked.json",
'type' => 'OrderedCollection',
'totalItems' => Fave::countByProfile($profile),
'orderedItems' => $faves
];
ActivityPubReturn::answer($res);
}
/**
* Take a fave object and turns it in a pretty array to be used
* as a plugin answer
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Fave $fave_object
* @return array pretty array representating a Fave
*/
protected function pretty_fave($fave_object)
{
$res = array("uri" => $fave_object->uri,
"created" => $fave_object->created,
"object" => Activitypub_notice::notice_to_array(Notice::getByID($fave_object->notice_id)));
return $res;
}
/**
* Fetch faves
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param int32 $user_id
* @param int32 $limit
* @param int32 $since_id
* @param int32 $max_id
* @return Fave fetchable fave collection
*/
private static function fetch_faves(
$user_id,
$limit = 40,
$since_id = null,
$max_id = null
) {
$fav = new Fave();
$fav->user_id = $user_id;
$fav->orderBy('modified DESC');
if ($since_id != null) {
$fav->whereAdd("notice_id > {$since_id}");
}
if ($max_id != null) {
$fav->whereAdd("notice_id < {$max_id}");
}
$fav->limit($limit);
$fav->find();
return $fav;
}
}

View File

@ -1,149 +0,0 @@
<?php
/**
* GNU social - a federating social network
*
* ActivityPubPlugin implementation for GNU Social
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
}
/**
* Actor's Liked Collection
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://www.gnu.org/software/social/
*/
class apActorLikedCollectionAction extends ManagedAction
{
protected $needLogin = false;
protected $canPost = true;
/**
* Handle the Liked Collection request
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return void
*/
protected function handle ()
{
$nickname = $this->trimmed ('nickname');
try {
$user = User::getByNickname ($nickname);
$profile = $user->getProfile ();
$url = $profile->profileurl;
} catch (Exception $e) {
ActivityPubReturn::error ('Invalid username.');
}
$limit = intval ($this->trimmed ('limit'));
$since_id = intval ($this->trimmed ('since_id'));
$max_id = intval ($this->trimmed ('max_id'));
$limit = empty ($limit) ? 40 : $limit; // Default is 40
$since_id = empty ($since_id) ? null : $since_id;
$max_id = empty ($max_id) ? null : $max_id;
// Max is 80
if ($limit > 80) {
$limit = 80;
}
$fave = $this->fetch_faves ($user->getID(), $limit, $since_id, $max_id);
$faves = array ();
while ($fave->fetch ()) {
$faves[] = $this->pretty_fave (clone ($fave));
}
$res = [
'@context' => [
"https://www.w3.org/ns/activitystreams",
[
"@language" => "en"
]
],
'id' => "{$url}/liked.json",
'type' => 'OrderedCollection',
'totalItems' => Fave::countByProfile ($profile),
'orderedItems' => $faves
];
ActivityPubReturn::answer ($res);
}
/**
* Take a fave object and turns it in a pretty array to be used
* as a plugin answer
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Fave $fave_object
* @return array pretty array representating a Fave
*/
protected function pretty_fave ($fave_object)
{
$res = array("uri" => $fave_object->uri,
"created" => $fave_object->created,
"object" => Activitypub_notice::notice_to_array (Notice::getByID ($fave_object->notice_id)));
return $res;
}
/**
* Fetch faves
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param int32 $user_id
* @param int32 $limit
* @param int32 $since_id
* @param int32 $max_id
* @return Fave fetchable fave collection
*/
private static function fetch_faves ($user_id, $limit = 40, $since_id = null,
$max_id = null)
{
$fav = new Fave ();
$fav->user_id = $user_id;
$fav->orderBy ('modified DESC');
if ($since_id != null) {
$fav->whereAdd ("notice_id > {$since_id}");
}
if ($max_id != null) {
$fav->whereAdd ("notice_id < {$max_id}");
}
$fav->limit ($limit);
$fav->find ();
return $fav;
}
}

View File

@ -19,14 +19,14 @@
*
* @category Plugin
* @package GNUsocial
* @author Daniel Supernault <danielsupernault@gmail.com>
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
@ -34,35 +34,44 @@ if (!defined ('GNUSOCIAL')) {
*
* @category Plugin
* @package GNUsocial
* @author Daniel Supernault <danielsupernault@gmail.com>
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://www.gnu.org/software/social/
*/
class apActorProfileAction extends ManagedAction
{
protected $needLogin = false;
protected $canPost = true;
protected $needLogin = false;
protected $canPost = true;
/**
* Handle the Actor Profile request
*
* @author Daniel Supernault <danielsupernault@gmail.com>
* @return void
*/
protected function handle()
{
$nickname = $this->trimmed ('nickname');
try {
$user = User::getByNickname ($nickname);
$profile = $user->getProfile ();
}
catch (Exception $e) {
ActivityPubReturn::error ('Invalid username.', 404);
}
$res = Activitypub_profile::profile_to_array ($profile);
ActivityPubReturn::answer ($res);
/**
* Handle the Actor Profile request
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return void
*/
protected function handle()
{
if (!empty($id = $this->trimmed('id'))) {
try {
$profile = Profile::getByID($id);
} catch (Exception $e) {
ActivityPubReturn::error('Invalid Actor URI.', 404);
}
unset($id);
} else {
try {
$profile = User::getByNickname($this->trimmed('nickname'))->getProfile();
} catch (Exception $e) {
ActivityPubReturn::error('Invalid username.', 404);
}
}
if (!$profile->isLocal()) {
ActivityPubReturn::error("This is not a local user.");
}
$res = Activitypub_profile::profile_to_array($profile);
ActivityPubReturn::answer($res);
}
}

View File

@ -0,0 +1,92 @@
<?php
/**
* GNU social - a federating social network
*
* ActivityPubPlugin implementation for GNU Social
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
* Authorize Remote Follow
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://www.gnu.org/software/social/
*/
class apAuthorizeRemoteFollowAction extends Action
{
/**
* Prepare to handle the Authorize Remote Follow request.
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param array $args
* @return boolean
*/
protected function prepare(array $args=array())
{
parent::prepare($args);
if (!common_logged_in()) {
// XXX: selfURL() didn't work. :<
common_set_returnto($_SERVER['REQUEST_URI']);
if (Event::handle('RedirectToLogin', array($this, null))) {
common_redirect(common_local_url('login'), 303);
}
return false;
} else {
if (!isset($_GET["acct"])) {
return false;
}
}
return true;
}
/**
* Handle the Authorize Remote Follow Request.
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
*/
protected function handle()
{
$other = Activitypub_profile::get_from_uri($_GET["acct"]);
$actor_profile = common_current_user()->getProfile();
$object_profile = $other->local_profile();
if (!Subscription::exists($actor_profile, $object_profile)) {
Subscription::start($actor_profile, $object_profile);
}
try {
$postman = new Activitypub_postman($actor_profile, [$other]);
$postman->follow();
} catch (Exception $e) {
// Meh, let the exception go on its merry way, it shouldn't be all
// that important really.
}
common_redirect(common_local_url('userbyid', array('id' => $other->profile_id)), 303);
}
}

239
actions/apremotefollow.php Normal file
View File

@ -0,0 +1,239 @@
<?php
/**
* GNU social - a federating social network
*
* ActivityPubPlugin implementation for GNU Social
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
* Remote Follow
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://www.gnu.org/software/social/
*/
class apRemoteFollowAction extends Action
{
public $nickname;
public $local_profile;
public $remote_identifier;
public $err;
/**
* Prepare to handle the Remote Follow request.
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param array $args
* @return boolean
*/
protected function prepare(array $args=array())
{
parent::prepare($args);
if (common_logged_in()) {
// TRANS: Client error.
$this->clientError(_m('You can use the local subscription!'));
}
// Local user the remote wants to subscribe to
$this->nickname = $this->trimmed('nickname');
$this->local_profile = User::getByNickname($this->nickname)->getProfile();
// Webfinger or profile URL of the remote user
$this->remote_identifier = $this->trimmed('remote_identifier');
return true;
}
/**
* Handle the Remote Follow Request.
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
*/
protected function handle()
{
parent::handle();
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
/* Use a session token for CSRF protection. */
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
// TRANS: Client error displayed when the session token does not match or is not given.
$this->showForm(_m('There was a problem with your session token. '.
'Try again, please.'));
return;
}
$this->activitypub_connect();
} else {
$this->showForm();
}
}
/**
* Form.
*
* @author GNU Social
* @param string|null $err
*/
public function showForm($err = null)
{
$this->err = $err;
if ($this->boolean('ajax')) {
$this->startHTML('text/xml;charset=utf-8');
$this->elementStart('head');
// TRANS: Form title.
$this->element('title', null, _m('TITLE', 'Subscribe to user'));
$this->elementEnd('head');
$this->elementStart('body');
$this->showContent();
$this->elementEnd('body');
$this->endHTML();
} else {
$this->showPage();
}
}
/**
* Page content.
*
* @author GNU Social
*/
public function showContent()
{
// TRANS: Form legend. %s is a nickname.
$header = sprintf(_m('Subscribe to %s'), $this->nickname);
// TRANS: Button text to subscribe to a profile.
$submit = _m('BUTTON', 'Subscribe');
$this->elementStart(
'form',
['id' => 'form_activitypub_connect',
'method' => 'post',
'class' => 'form_settings',
'action' => common_local_url(
'apRemoteFollow',
['nickname' => $this->nickname]
)
]
);
$this->elementStart('fieldset');
$this->element('legend', null, $header);
$this->hidden('token', common_session_token());
$this->elementStart('ul', 'form_data');
$this->elementStart('li', array('id' => 'activitypub_nickname'));
// TRANS: Field label.
$this->input(
'nickname',
_m('User nickname'),
$this->nickname,
// TRANS: Field title.
_m('Nickname of the user you want to follow.')
);
$this->elementEnd('li');
$this->elementStart('li', array('id' => 'activitypub_profile'));
// TRANS: Field label.
$this->input(
'remote_identifier',
_m('Profile Account'),
$this->remote_identifier,
// TRANS: Tooltip for field label "Profile Account".
_m('Your account ID (e.g. user@example.net).')
);
$this->elementEnd('li');
$this->elementEnd('ul');
$this->submit('submit', $submit);
$this->elementEnd('fieldset');
$this->elementEnd('form');
}
/**
* Start connecting the two instances (will be finished with the authorization)
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return void
*/
public function activitypub_connect()
{
$remote_profile = null;
try { // Try with ActivityPub system
$remote_profile = Activitypub_profile::get_from_uri($this->remote_identifier);
} catch (Exception $e) { // Fallback to compatibility WebFinger system
$validate = new Validate();
$opts = array('allowed_schemes' => array('http', 'https', 'acct'));
if ($validate->uri($this->remote_identifier, $opts)) {
$bits = parse_url($this->remote_identifier);
if ($bits['scheme'] == 'acct') {
$remote_profile = $this->connect_webfinger($bits['path']);
}
} elseif (strpos($this->remote_identifier, '@') !== false) {
$remote_profile = $this->connect_webfinger($this->remote_identifier);
}
}
if (!empty($remote_profile)) {
$url = ActivityPubPlugin::stripUrlPath($remote_profile->get_uri())."activitypub/authorize_follow?acct=".$this->local_profile->getUri();
common_log(LOG_INFO, "Sending remote subscriber $this->remote_identifier to $url");
common_redirect($url, 303);
return;
}
// TRANS: Client error.
$this->clientError(_m('Must provide a remote profile.'));
}
/**
* This function is used by activitypub_connect () and
* is a step of the process
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param type $acct
* @return Profile Profile resulting of WebFinger connection
*/
private function connect_webfinger($acct)
{
$link = ActivityPubPlugin::pull_remote_profile($acct);
if (!is_null($link)) {
return $link;
}
// TRANS: Client error.
$this->clientError(_m('Could not confirm remote profile address.'));
}
/**
* Page title
*
* @return string Page title
*/
public function title()
{
// TRANS: Page title.
return _m('ActivityPub Connect');
}
}

View File

@ -25,8 +25,8 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
@ -40,80 +40,55 @@ if (!defined ('GNUSOCIAL')) {
*/
class apSharedInboxAction extends ManagedAction
{
protected $needLogin = false;
protected $canPost = true;
protected $needLogin = false;
protected $canPost = true;
/**
* Handle the Shared Inbox request
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return void
*/
protected function handle ()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
ActivityPubReturn::error ("Only POST requests allowed.");
}
/**
* Handle the Shared Inbox request
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return void
*/
protected function handle()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
ActivityPubReturn::error("Only POST requests allowed.");
}
$data = json_decode (file_get_contents ('php://input'));
$data = json_decode(file_get_contents('php://input'));
// Validate data
if (!isset ($data->type)) {
ActivityPubReturn::error ("Type was not specified.");
}
if (!isset ($data->actor)) {
ActivityPubReturn::error ("Actor was not specified.");
}
if (!isset ($data->object)) {
ActivityPubReturn::error ("Object was not specified.");
}
// Validate data
if (!isset($data->type)) {
ActivityPubReturn::error("Type was not specified.");
}
if (!isset($data->actor)) {
ActivityPubReturn::error("Actor was not specified.");
}
if (!isset($data->object)) {
ActivityPubReturn::error("Object was not specified.");
}
$discovery = new Activitypub_explorer;
$discovery = new Activitypub_explorer;
// Get valid Actor object
try {
$actor_profile = $discovery->lookup($data->actor);
$actor_profile = $actor_profile[0];
} catch (Exception $e) {
ActivityPubReturn::error("Invalid Actor.", 404);
}
unset($discovery);
// Get valid Actor object
try {
$actor_profile = $discovery->lookup ($data->actor);
$actor_profile = $actor_profile[0];
} catch (Exception $e) {
ActivityPubReturn::error ("Invalid Actor.", 404);
}
// Public To:
$public_to = ["https://www.w3.org/ns/activitystreams#Public",
"Public",
"as:Public"
];
unset ($discovery);
$to_profiles = "https://www.w3.org/ns/activitystreams#Public";
// Public To:
$public_to = array ("https://www.w3.org/ns/activitystreams#Public",
"Public",
"as:Public");
// Process request
switch ($data->type) {
// Process request
switch ($data->type) {
case "Create":
if (!isset($data->to)) {
ActivityPubReturn::error ("To was not specified.");
}
$discovery = new Activitypub_explorer;
$to_profiles = array ();
// Generate To objects
if (is_array ($data->to)) {
// Remove duplicates from To actors set
array_unique ($data->to);
foreach ($data->to as $to_url) {
try {
$to_profiles = array_merge ($to_profiles, $discovery->lookup ($to_url));
} catch (Exception $e) {
// XXX: Invalid actor found, not sure how we handle those
}
}
} else if (empty ($data->to) || in_array ($data->to, $public_to)) {
// No need to do anything else at this point, let's just break out the if
} else {
try {
$to_profiles[]= $discovery->lookup ($data->to);
} catch (Exception $e) {
ActivityPubReturn::error ("Invalid Actor.", 404);
}
}
unset ($discovery);
require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Create.php";
break;
case "Follow":
@ -131,8 +106,14 @@ class apSharedInboxAction extends ManagedAction
case "Delete":
require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Delete.php";
break;
case "Accept":
require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Accept.php";
break;
case "Reject":
require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Reject.php";
break;
default:
ActivityPubReturn::error ("Invalid type value.");
ActivityPubReturn::error("Invalid type value.");
}
}
}

58
actions/inbox/Accept.php Normal file
View File

@ -0,0 +1,58 @@
<?php
/**
* GNU social - a federating social network
*
* ActivityPubPlugin implementation for GNU Social
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined('GNUSOCIAL')) {
exit(1);
}
// Validate data
if (!isset($data->type)) {
ActivityPubReturn::error("Type was not specified.");
}
switch ($data->object->type) {
case "Follow":
// Validate data
if (!isset($data->object->object)) {
ActivityPubReturn::error("Object Actor URL was not specified.");
}
// Get valid Object profile
try {
$object_profile = new Activitypub_explorer;
$object_profile = $object_profile->lookup($data->object->object)[0];
} catch (Exception $e) {
ActivityPubReturn::error("Invalid Object Actor URL.", 404);
}
$pending_list = new Activitypub_pending_follow_requests($actor_profile->getID(), $object_profile->getID());
$pending_list->remove();
ActivityPubReturn::answer($data); // You are now being followed by this person.
break;
default:
ActivityPubReturn::error("Invalid object type.");
break;
}

View File

@ -25,13 +25,13 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
try {
Notice::getByUri ($data->object)->repeat ($actor_profile, "ActivityPub");
ActivityPubReturn::answer ("Notice repeated successfully.");
Notice::getByUri($data->object->id)->repeat($actor_profile, "ActivityPub");
ActivityPubReturn::answer("Notice repeated successfully.");
} catch (Exception $e) {
ActivityPubReturn::error ($e->getMessage (), 403);
ActivityPubReturn::error($e->getMessage(), 403);
}

View File

@ -25,79 +25,109 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
$valid_object_types = array ("Note");
$valid_object_types = array("Note");
// Validate data
if (!(isset ($data->object->type) && in_array ($data->object->type, $valid_object_types))) {
ActivityPubReturn::error ("Invalid Object type.");
if (!isset($data->id)) {
ActivityPubReturn::error("Id not specified.");
}
if (!isset ($data->object->content)) {
ActivityPubReturn::error ("Object content was not specified.");
if (!(isset($data->object->type) && in_array($data->object->type, $valid_object_types))) {
ActivityPubReturn::error("Invalid Object type.");
}
if (!isset ($data->object->url)) {
ActivityPubReturn::error ("Object url was not specified.");
} else if (!filter_var ($data->object->url, FILTER_VALIDATE_URL)) {
ActivityPubReturn::error ("Invalid Object Url.");
if (!isset($data->object->content)) {
ActivityPubReturn::error("Object content was not specified.");
}
if (!isset($data->object->url)) {
ActivityPubReturn::error("Object url was not specified.");
} elseif (!filter_var($data->object->url, FILTER_VALIDATE_URL)) {
ActivityPubReturn::error("Invalid Object Url.");
}
if (!isset($data->object->to)) {
ActivityPubReturn::error("Object To was not specified.");
}
$content = $data->object->content;
$act = new Activity ();
$act = new Activity();
$act->verb = ActivityVerb::POST;
$act->time = time ();
$act->actor = $actor_profile->asActivityObject ();
$act->time = time();
$act->actor = $actor_profile->asActivityObject();
$act->context = new ActivityContext ();
$act->context = new ActivityContext();
// Is this a reply?
if (isset ($data->object->reply_to)) {
try {
$reply_to = Notice::getByUri ($data->object->reply_to);
} catch (Exception $e) {
ActivityPubReturn::error ("Invalid Object reply_to value.");
}
$act->context->replyToID = $reply_to->getUri ();
$act->context->replyToUrl = $reply_to->getUrl ();
if (isset($data->object->reply_to)) {
try {
$reply_to = Notice::getByUri($data->object->reply_to);
} catch (Exception $e) {
ActivityPubReturn::error("Invalid Object reply_to value.");
}
$act->context->replyToID = $reply_to->getUri();
$act->context->replyToUrl = $reply_to->getUrl();
} else {
$reply_to = null;
$reply_to = null;
}
$act->context->attention = common_get_attentions ($content, $actor_profile, $reply_to);
$act->context->attention = common_get_attentions($content, $actor_profile, $reply_to);
foreach ($to_profiles as $to)
{
$act->context->attention[$to->getUri ()] = "http://activitystrea.ms/schema/1.0/person";
$discovery = new Activitypub_explorer;
if ($to_profiles == "https://www.w3.org/ns/activitystreams#Public") {
$to_profiles = array();
}
// Generate To objects
if (is_array($data->object->to)) {
// Remove duplicates from To actors set
array_unique($data->object->to);
foreach ($data->object->to as $to_url) {
try {
$to_profiles = array_merge($to_profiles, $discovery->lookup($to_url));
} catch (Exception $e) {
// XXX: Invalid actor found, not sure how we handle those
}
}
} elseif (empty($data->object->to) || in_array($data->object->to, $public_to)) {
// No need to do anything else at this point, let's just break out the if
} else {
try {
$to_profiles[]= $discovery->lookup($data->object->to);
} catch (Exception $e) {
ActivityPubReturn::error("Invalid Actor.", 404);
}
}
unset($discovery);
foreach ($to_profiles as $to) {
$act->context->attention[ActivityPubPlugin::actor_uri($to)] = "http://activitystrea.ms/schema/1.0/person";
}
// Reject notice if it is too long (without the HTML)
// This is done after MediaFile::fromUpload etc. just to act the same as the ApiStatusesUpdateAction
if (Notice::contentTooLong ($content)) {
ActivityPubReturn::error ("That's too long. Maximum notice size is %d character.");
if (Notice::contentTooLong($content)) {
ActivityPubReturn::error("That's too long. Maximum notice size is %d character.");
}
$options = array ('source' => 'ActivityPub', 'uri' => $data->id, 'url' => $data->object->url);
$options = array('source' => 'ActivityPub', 'uri' => isset($data->id) ? $data->id : $data->object->url, 'url' => $data->object->url);
// $options gets filled with possible scoping settings
ToSelector::fillActivity ($this, $act, $options);
ToSelector::fillActivity($this, $act, $options);
$actobj = new ActivityObject ();
$actobj = new ActivityObject();
$actobj->type = ActivityObject::NOTE;
$actobj->content = common_render_content ($content, $actor_profile, $reply_to);
$actobj->content = common_render_content($content, $actor_profile, $reply_to);
// Finally add the activity object to our activity
$act->objects[] = $actobj;
try {
$res = array ("@context" => "https://www.w3.org/ns/activitystreams",
"id" => $data->id,
"url" => $data->object->url,
"type" => "Create",
"actor" => $data->actor,
"object" => Activitypub_notice::notice_to_array (Notice::saveActivity ($act, $actor_profile, $options)));
ActivityPubReturn::answer ($res);
$res = Activitypub_create::create_to_array(
$data->id,
$data->actor,
Activitypub_notice::notice_to_array(Notice::saveActivity($act, $actor_profile, $options))
);
ActivityPubReturn::answer($res);
} catch (Exception $e) {
ActivityPubReturn::error ($e->getMessage ());
ActivityPubReturn::error($e->getMessage());
}

View File

@ -25,17 +25,15 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
try {
Notice::getByUri ($data->object)->deleteAs ($actor_profile);
$res = array ("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Delete",
"actor" => $data->actor,
"object" => $data->object);
ActivityPubReturn::answer ($res);
$notice = Notice::getByUri($data->object->id);
$notice_to_array = Activitypub_notice::notice_to_array($notice);
$notice->deleteAs($actor_profile);
ActivityPubReturn::answer(Activitypub_delete::delete_to_array($notice_to_array));
} catch (Exception $e) {
ActivityPubReturn::error ($e->getMessage (), 403);
ActivityPubReturn::error($e->getMessage(), 403);
}

View File

@ -25,34 +25,30 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
// Validate Object
if (!is_string ($data->object)) {
ActivityPubReturn::error ("Invalid Object object, URL expected.");
if (!is_string($data->object)) {
ActivityPubReturn::error("Invalid Object object, URL expected.");
}
// Get valid Object profile
try {
$object_profile = new Activitypub_explorer;
$object_profile = $object_profile->lookup ($data->object)[0];
} catch(Exception $e) {
ActivityPubReturn::error ("Invalid Object Actor URL.", 404);
$object_profile = new Activitypub_explorer;
$object_profile = $object_profile->lookup($data->object)[0];
} catch (Exception $e) {
ActivityPubReturn::error("Invalid Object Actor URL.", 404);
}
try {
if (!Subscription::exists ($actor_profile, $object_profile)) {
Subscription::start ($actor_profile, $object_profile);
$res = array ("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Follow",
"actor" => $data->actor,
"object" => $data->object);
ActivityPubReturn::answer ($res);
} else {
ActivityPubReturn::error ("Already following.", 409);
}
if (!Subscription::exists($actor_profile, $object_profile)) {
Subscription::start($actor_profile, $object_profile);
ActivityPubReturn::answer(Activitypub_accept::accept_to_array(Activitypub_follow::follow_to_array($data->actor, $data->object)));
} else {
ActivityPubReturn::error("Already following.", 409);
}
} catch (Exception $e) {
ActivityPubReturn::error ("Invalid Object Actor URL.", 404);
ActivityPubReturn::error("Invalid Object Actor URL.", 404);
}

View File

@ -25,17 +25,17 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
if (!isset($data->object->id)) {
ActivityPubReturn::error("Id not specified.");
}
try {
Fave::addNew ($actor_profile, Notice::getByUri ($data->object));
$res = array ("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Like",
"actor" => $data->actor,
"object" => $data->object);
ActivityPubReturn::answer ($res);
Fave::addNew($actor_profile, Notice::getByUri($data->object->id));
ActivityPubReturn::answer(Activitypub_like::like_to_array(Activitypub_notice::notice_to_array($data->actor, json_decode($data->object))));
} catch (Exception $e) {
ActivityPubReturn::error ($e->getMessage (), 403);
ActivityPubReturn::error($e->getMessage(), 403);
}

32
actions/inbox/Reject.php Normal file
View File

@ -0,0 +1,32 @@
<?php
/**
* GNU social - a federating social network
*
* ActivityPubPlugin implementation for GNU Social
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined('GNUSOCIAL')) {
exit(1);
}
// This is a dummy file as there is nothing to do if we fall in this case

View File

@ -25,53 +25,69 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
// Validate data
if (!isset ($data->type)) {
ActivityPubReturn::error ("Type was not specified.");
if (!isset($data->type)) {
ActivityPubReturn::error("Type was not specified.");
}
switch ($data->object->type) {
case "Like":
try {
// Validate data
if (!isset ($data->object->object)) {
ActivityPubReturn::error ("Object Notice URL was not specified.");
}
Fave::removeEntry ($actor_profile, Notice::getByUri ($data->object->object));
ActivityPubReturn::answer ("Notice disfavorited successfully.");
// Validate data
if (!isset($data->object->object->id)) {
ActivityPubReturn::error("Notice ID was not specified.");
}
Fave::removeEntry($actor_profile, Notice::getByUri($data->object->object->id));
// Notice disfavorited successfully.
ActivityPubReturn::answer(
Activitypub_undo::undo_to_array(
Activitypub_like::like_to_array(
Activitypub_notice::notice_to_array(
$actor_profile->getUrl(),
$data->object->object
)
)
)
);
} catch (Exception $e) {
ActivityPubReturn::error ($e->getMessage (), 403);
ActivityPubReturn::error($e->getMessage(), 403);
}
break;
case "Follow":
// Validate data
if (!isset ($data->object->object)) {
ActivityPubReturn::error ("Object Actor URL was not specified.");
if (!isset($data->object->object)) {
ActivityPubReturn::error("Object Actor URL was not specified.");
}
// Get valid Object profile
try {
$object_profile = new Activitypub_explorer;
$object_profile = $object_profile->lookup ($data->object->object)[0];
$object_profile = new Activitypub_explorer;
$object_profile = $object_profile->lookup($data->object->object)[0];
} catch (Exception $e) {
ActivityPubReturn::error ("Invalid Object Actor URL.", 404);
ActivityPubReturn::error("Invalid Object Actor URL.", 404);
}
try {
if (Subscription::exists ($actor_profile, $object_profile)) {
Subscription::cancel ($actor_profile, $object_profile);
ActivityPubReturn::answer ("You are no longer following this person.");
} else {
ActivityPubReturn::error ("You are not following this person already.", 409);
}
} catch (Exception $e) {
ActivityPubReturn::error ("Invalid Object Actor URL.", 404);
if (Subscription::exists($actor_profile, $object_profile)) {
Subscription::cancel($actor_profile, $object_profile);
// You are no longer following this person.
ActivityPubReturn::answer(
Activitypub_undo::undo_to_array(
Activitypub_accept::accept_to_array(
Activitypub_follow::follow_to_array(
$actor_profile->getUrl(),
$object_profile->getUrl()
)
)
)
);
} else {
ActivityPubReturn::error("You are not following this person already.", 409);
}
break;
default:
ActivityPubReturn::error ("Invalid object type.");
ActivityPubReturn::error("Invalid object type.");
break;
}

View File

@ -0,0 +1,58 @@
<?php
/**
* GNU social - a federating social network
*
* ActivityPubPlugin implementation for GNU Social
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
* ActivityPub error representation
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://www.gnu.org/software/social/
*/
class Activitypub_accept extends Managed_DataObject
{
/**
* Generates an ActivityPub representation of a Accept
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param array $object
* @return pretty array to be used in a response
*/
public static function accept_to_array($object)
{
$res = array("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Accept",
"object" => $object
);
return $res;
}
}

View File

@ -0,0 +1,59 @@
<?php
/**
* GNU social - a federating social network
*
* ActivityPubPlugin implementation for GNU Social
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
* ActivityPub error representation
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://www.gnu.org/software/social/
*/
class Activitypub_announce extends Managed_DataObject
{
/**
* Generates an ActivityPub representation of a Announce
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param array $object
* @return pretty array to be used in a response
*/
public static function announce_to_array($actor, $object)
{
$res = array("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Announce",
"actor" => $actor,
"object" => $object
);
return $res;
}
}

View File

@ -25,8 +25,8 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
@ -40,39 +40,38 @@ if (!defined ('GNUSOCIAL')) {
*/
class Activitypub_attachment extends Managed_DataObject
{
/**
* Generates a pretty array from an Attachment object
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Attachment $attachment
* @return pretty array to be used in a response
*/
public static function attachment_to_array ($attachment)
{
$res = [
/**
* Generates a pretty array from an Attachment object
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Attachment $attachment
* @return pretty array to be used in a response
*/
public static function attachment_to_array($attachment)
{
$res = [
'@context' => [
"https://www.w3.org/ns/activitystreams",
[
"@language" => "en"
]
],
'id' => $attachment->getID (),
'id' => $attachment->getID(),
'mimetype' => $attachment->mimetype,
'url' => $attachment->getUrl (),
'url' => $attachment->getUrl(),
'size' => intval($attachment->size), // $attachment->getSize ()
'title' => $attachment->getTitle (),
'title' => $attachment->getTitle(),
'meta' => null
];
// Image
if (substr ($res["mimetype"], 0, 5) == "image")
{
$res["meta"]= [
// Image
if (substr($res["mimetype"], 0, 5) == "image") {
$res["meta"]= [
'width' => $attachment->width,
'height' => $attachment->height
];
}
return $res;
}
return $res;
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* GNU social - a federating social network
*
* ActivityPubPlugin implementation for GNU Social
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
* ActivityPub error representation
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://www.gnu.org/software/social/
*/
class Activitypub_create extends Managed_DataObject
{
/**
* Generates an ActivityPub representation of a Create
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $actor
* @param array $object
* @return pretty array to be used in a response
*/
public static function create_to_array($id, $actor, $object)
{
$res = array("@context" => "https://www.w3.org/ns/activitystreams",
"id" => $id,
"type" => "Create",
"actor" => $actor,
"object" => $object
);
return $res;
}
}

View File

@ -0,0 +1,59 @@
<?php
/**
* GNU social - a federating social network
*
* ActivityPubPlugin implementation for GNU Social
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
* ActivityPub error representation
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://www.gnu.org/software/social/
*/
class Activitypub_delete extends Managed_DataObject
{
/**
* Generates an ActivityPub representation of a Delete
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param array $object
* @return pretty array to be used in a response
*/
public static function delete_to_array($object)
{
$res = array("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Delete",
"actor" => $object["actor"],
"object" => $object
);
return $res;
}
}

View File

@ -25,8 +25,8 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
@ -40,18 +40,18 @@ if (!defined ('GNUSOCIAL')) {
*/
class Activitypub_error extends Managed_DataObject
{
/**
* Generates a pretty error from a string
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $m
* @return pretty array to be used in a response
*/
public static function error_message_to_array ($m)
{
$res = [
/**
* Generates a pretty error from a string
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $m
* @return pretty array to be used in a response
*/
public static function error_message_to_array($m)
{
$res = [
'error'=> $m
];
return $res;
}
return $res;
}
}

View File

@ -0,0 +1,60 @@
<?php
/**
* GNU social - a federating social network
*
* ActivityPubPlugin implementation for GNU Social
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
* ActivityPub error representation
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://www.gnu.org/software/social/
*/
class Activitypub_follow extends Managed_DataObject
{
/**
* Generates an ActivityPub representation of a subscription
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $actor
* @param string $object
* @return pretty array to be used in a response
*/
public static function follow_to_array($actor, $object)
{
$res = array("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Follow",
"actor" => $actor,
"object" => $object
);
return $res;
}
}

View File

@ -0,0 +1,60 @@
<?php
/**
* GNU social - a federating social network
*
* ActivityPubPlugin implementation for GNU Social
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
* ActivityPub error representation
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://www.gnu.org/software/social/
*/
class Activitypub_like extends Managed_DataObject
{
/**
* Generates an ActivityPub representation of a Like
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $actor
* @param array $object
* @return pretty array to be used in a response
*/
public static function like_to_array($actor, $object)
{
$res = array("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Like",
"actor" => $actor,
"object" => $object
);
return $res;
}
}

View File

@ -25,8 +25,8 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
@ -41,51 +41,51 @@ if (!defined ('GNUSOCIAL')) {
*/
class Activitypub_notice extends Managed_DataObject
{
/**
* Generates a pretty notice from a Notice object
*
* @author Daniel Supernault <danielsupernault@gmail.com>
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Notice $notice
* @return pretty array to be used in a response
*/
public static function notice_to_array ($notice)
{
$attachments = array ();
foreach($notice->attachments () as $attachment) {
$attachments[] = Activitypub_attachment::attachment_to_array ($attachment);
}
/**
* Generates a pretty notice from a Notice object
*
* @author Daniel Supernault <danielsupernault@gmail.com>
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Notice $notice
* @return pretty array to be used in a response
*/
public static function notice_to_array($notice)
{
$attachments = array();
foreach ($notice->attachments() as $attachment) {
$attachments[] = Activitypub_attachment::attachment_to_array($attachment);
}
$tags = array ();
foreach($notice->getTags()as $tag) {
if ($tag != "") { // Hacky workaround to avoid stupid outputs
$tags[] = Activitypub_tag::tag_to_array ($tag);
}
}
$tags = array();
foreach ($notice->getTags() as $tag) {
if ($tag != "") { // Hacky workaround to avoid stupid outputs
$tags[] = Activitypub_tag::tag_to_array($tag);
}
}
$to = array ();
foreach ($notice->getAttentionProfileIDs () as $to_id) {
$to[] = Profile::getById ($to_id)->getUri ();
}
if (!is_null($to)) {
$to = array ("https://www.w3.org/ns/activitystreams#Public");
}
$to = array();
foreach ($notice->getAttentionProfiles() as $to_profile) {
$to[] = $to_profile->getUri();
}
if (empty($to)) {
$to = array("https://www.w3.org/ns/activitystreams#Public");
}
$item = [
'id' => $notice->getUrl (),
'type' => 'Notice',
'actor' => $notice->getProfile ()->getUrl (),
'published' => $notice->getCreated (),
$item = [
'id' => $notice->getUri(),
'type' => 'Note',
'actor' => $notice->getProfile()->getUrl(),
'published' => $notice->getCreated(),
'to' => $to,
'content' => $notice->getContent (),
'url' => $notice->getUrl (),
'reply_to' => empty($notice->reply_to) ? null : Notice::getById($notice->reply_to)->getUrl (),
'is_local' => $notice->isLocal (),
'conversation' => intval ($notice->conversation),
'content' => $notice->getContent(),
'url' => $notice->getUrl(),
'reply_to' => empty($notice->reply_to) ? null : Notice::getById($notice->reply_to)->getUri(),
'is_local' => $notice->isLocal(),
'conversation' => intval($notice->conversation),
'attachment' => $attachments,
'tag' => $tags
];
return $item;
}
return $item;
}
}

View File

@ -0,0 +1,105 @@
<?php
/**
* GNU social - a federating social network
*
* ActivityPubPlugin implementation for GNU Social
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
* ActivityPub's Pending follow requests
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://www.gnu.org/software/social/
*/
class Activitypub_pending_follow_requests extends Managed_DataObject
{
public $__table = 'Activitypub_pending_follow_requests';
public $local_profile_id;
public $remote_profile_id;
private $_reldb = null;
/**
* Return table definition for Schema setup and DB_DataObject usage.
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return array array of column definitions
*/
public static function schemaDef()
{
return array(
'fields' => array(
'local_profile_id' => array('type' => 'integer', 'not null' => true),
'remote_profile_id' => array('type' => 'integer', 'not null' => true),
'relation_id' => array('type' => 'serial', 'not null' => true),
),
'primary key' => array('relation_id'),
'unique keys' => array(
'Activitypub_pending_follow_requests_relation_id_key' => array('relation_id'),
),
'foreign keys' => array(
'Activitypub_pending_follow_requests_local_profile_id_fkey' => array('profile', array('local_profile_id' => 'id')),
'Activitypub_pending_follow_requests_remote_profile_id_fkey' => array('profile', array('remote_profile_id' => 'id')),
),
);
}
public function __construct($actor, $remote_actor)
{
$this->local_profile_id = $actor;
$this->remote_profile_id = $remote_actor;
}
/**
* Add Follow request to table.
*
* @author Diogo Cordeiro
* @param int32 $actor actor id
* @param int32 $remote_actor remote actor id
*/
public function add()
{
return !$this->exists() && $this->insert();
}
public function exists()
{
$this->_reldb = clone ($this);
if ($this->_reldb->find() > 0) {
$this->_reldb->fetch();
return true;
}
return false;
}
public function remove()
{
return $this->exists() && $this->_reldb->delete();
}
}

View File

@ -25,8 +25,8 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
@ -35,331 +35,337 @@ if (!defined ('GNUSOCIAL')) {
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://www.gnu.org/software/social/
*/
class Activitypub_profile extends Profile
{
public $__table = 'Activitypub_profile';
public $__table = 'Activitypub_profile';
protected $_profile = null;
protected $_profile = null;
/**
* Return table definition for Schema setup and DB_DataObject usage.
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return array array of column definitions
*/
static function schemaDef ()
{
return array (
'fields' => array (
'uri' => array ('type' => 'varchar', 'length' => 191, 'not null' => true),
'profile_id' => array ('type' => 'integer'),
'inboxuri' => array ('type' => 'varchar', 'length' => 191),
'sharedInboxuri' => array ('type' => 'varchar', 'length' => 191),
'created' => array ('type' => 'datetime', 'not null' => true),
'modified' => array ('type' => 'datetime', 'not null' => true),
/**
* Return table definition for Schema setup and DB_DataObject usage.
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return array array of column definitions
*/
public static function schemaDef()
{
return array(
'fields' => array(
'uri' => array('type' => 'varchar', 'length' => 191, 'not null' => true),
'profile_id' => array('type' => 'integer'),
'inboxuri' => array('type' => 'varchar', 'length' => 191),
'sharedInboxuri' => array('type' => 'varchar', 'length' => 191),
'created' => array('type' => 'datetime', 'not null' => true),
'modified' => array('type' => 'datetime', 'not null' => true),
),
'primary key' => array ('uri'),
'unique keys' => array (
'Activitypub_profile_profile_id_key' => array ('profile_id'),
'Activitypub_profile_inboxuri_key' => array ('inboxuri'),
'primary key' => array('uri'),
'unique keys' => array(
'Activitypub_profile_profile_id_key' => array('profile_id'),
'Activitypub_profile_inboxuri_key' => array('inboxuri'),
),
'foreign keys' => array (
'Activitypub_profile_profile_id_fkey' => array ('profile', array ('profile_id' => 'id')),
'foreign keys' => array(
'Activitypub_profile_profile_id_fkey' => array('profile', array('profile_id' => 'id')),
),
);
}
}
/**
* Generates a pretty profile from a Profile object
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Profile $profile
* @return pretty array to be used in a response
*/
public static function profile_to_array ($profile)
{
$url = $profile->getURL ();
$res = [
/**
* Generates a pretty profile from a Profile object
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Profile $profile
* @return pretty array to be used in a response
*/
public static function profile_to_array($profile)
{
$uri = ActivityPubPlugin::actor_uri($profile);
$id = $profile->getID();
$res = [
'@context' => [
"https://www.w3.org/ns/activitystreams",
[
"@language" => "en"
]
],
'id' => $profile->getID (),
'type' => 'Person',
'nickname' => $profile->getNickname (),
'is_local' => $profile->isLocal (),
'inbox' => "{$url}/inbox.json",
'sharedInbox' => common_root_url ()."inbox.json",
'outbox' => "{$url}/outbox.json",
'display_name' => $profile->getFullname (),
'followers' => "{$url}/followers.json",
'followers_count' => $profile->subscriberCount (),
'following' => "{$url}/following.json",
'following_count' => $profile->subscriptionCount (),
'liked' => "{$url}/liked.json",
'liked_count' => Fave::countByProfile ($profile),
'summary' => ($desc = $profile->getDescription ()) == null ? "" : $desc,
'url' => $profile->getURL (),
'avatar' => [
'id' => $uri,
'type' => 'Person',
'preferredUsername' => $profile->getNickname(),
'is_local' => $profile->isLocal(),
'inbox' => common_local_url("apActorInbox", array("id" => $id)),
'name' => $profile->getFullname(),
'followers' => common_local_url("apActorFollowers", array("id" => $id)),
'followers_count' => $profile->subscriberCount(),
'following' => common_local_url("apActorFollowing", array("id" => $id)),
'following_count' => $profile->subscriptionCount(),
'liked' => common_local_url("apActorLiked", array("id" => $id)),
'liked_count' => Fave::countByProfile($profile),
'summary' => ($desc = $profile->getDescription()) == null ? "" : $desc,
'icon' => [
'type' => 'Image',
'width' => 96,
'height' => 96,
'url' => $profile->avatarUrl (AVATAR_PROFILE_SIZE)
'width' => AVATAR_PROFILE_SIZE,
'height' => AVATAR_PROFILE_SIZE,
'url' => $profile->avatarUrl(AVATAR_PROFILE_SIZE)
]
];
return $res;
if ($profile->isLocal()) {
$res["sharedInbox"] = common_local_url("apSharedInbox", array("id" => $id));
} else {
$aprofile = new Activitypub_profile();
$aprofile = $aprofile->from_profile($profile);
$res["sharedInbox"] = $aprofile->sharedInboxuri;
}
/**
* Insert the current objects variables into the database
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @access public
* @throws ServerException
*/
public function do_insert ()
{
$profile = new Profile ();
return $res;
}
$profile->created = $this->created = $this->modified = common_sql_now ();
/**
* Insert the current objects variables into the database
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @access public
* @throws ServerException
*/
public function do_insert()
{
$profile = new Profile();
$fields = array (
'uri' => 'profileurl',
'nickname' => 'nickname',
'fullname' => 'fullname',
'bio' => 'bio'
);
$profile->created = $this->created = $this->modified = common_sql_now();
foreach ($fields as $af => $pf) {
$profile->$pf = $this->$af;
}
$fields = [
'uri' => 'profileurl',
'nickname' => 'nickname',
'fullname' => 'fullname',
'bio' => 'bio'
];
$this->profile_id = $profile->insert ();
if ($this->profile_id === false) {
$profile->query ('ROLLBACK');
throw new ServerException ('Profile insertion failed.');
}
$ok = $this->insert ();
if ($ok === false) {
$profile->query ('ROLLBACK');
throw new ServerException ('Cannot save ActivityPub profile.');
}
foreach ($fields as $af => $pf) {
$profile->$pf = $this->$af;
}
/**
* Fetch the locally stored profile for this Activitypub_profile
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return Profile
* @throws NoProfileException if it was not found
*/
public function local_profile ()
{
$profile = Profile::getKV ('id', $this->profile_id);
if (!$profile instanceof Profile) {
throw new NoProfileException ($this->profile_id);
}
return $profile;
$this->profile_id = $profile->insert();
if ($this->profile_id === false) {
$profile->query('ROLLBACK');
throw new ServerException('Profile insertion failed.');
}
/**
* Generates an Activitypub_profile from a Profile
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Profile $profile
* @return Activitypub_profile
* @throws Exception if no Activitypub_profile exists for given Profile
*/
static function from_profile (Profile $profile)
{
$profile_id = $profile->getID ();
$ok = $this->insert();
$aprofile = self::getKV ('profile_id', $profile_id);
if (!$aprofile instanceof Activitypub_profile) {
// No Activitypub_profile for this profile_id,
if (!$profile->isLocal ()) {
// create one!
$aprofile = self::create_from_local_profile ($profile);
} else {
throw new Exception ('No Activitypub_profile for Profile ID: '.$profile_id. ', this is a local user.');
}
}
if ($ok === false) {
$profile->query('ROLLBACK');
throw new ServerException('Cannot save ActivityPub profile.');
}
}
foreach ($profile as $key => $value) {
$aprofile->$key = $value;
}
/**
* Fetch the locally stored profile for this Activitypub_profile
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return Profile
* @throws NoProfileException if it was not found
*/
public function local_profile()
{
$profile = Profile::getKV('id', $this->profile_id);
if (!$profile instanceof Profile) {
throw new NoProfileException($this->profile_id);
}
return $profile;
}
return $aprofile;
/**
* Generates an Activitypub_profile from a Profile
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Profile $profile
* @return Activitypub_profile
* @throws Exception if no Activitypub_profile exists for given Profile
*/
public static function from_profile(Profile $profile)
{
$profile_id = $profile->getID();
$aprofile = self::getKV('profile_id', $profile_id);
if (!$aprofile instanceof Activitypub_profile) {
// No Activitypub_profile for this profile_id,
if (!$profile->isLocal()) {
// create one!
$aprofile = self::create_from_local_profile($profile);
} else {
throw new Exception('No Activitypub_profile for Profile ID: '.$profile_id. ', this is a local user.');
}
}
/**
* Given an existent local profile creates an ActivityPub profile.
* One must be careful not to give a user profile to this function
* as only remote users have ActivityPub_profiles on local instance
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Profile $profile
* @return Activitypub_profile
*/
private static function create_from_local_profile (Profile $profile)
{
$url = $profile->getURL ();
$inboxes = Activitypub_explorer::get_actor_inboxes_uri ($url);
$aprofile->created = $aprofile->modified = common_sql_now ();
$aprofile = new Activitypub_profile;
$aprofile->profile_id = $profile->getID ();
$aprofile->uri = $url;
$aprofile->nickname = $profile->getNickname ();
$aprofile->fullname = $profile->getFullname ();
$aprofile->bio = substr ($profile->getDescription (), 0, 1000);
$aprofile->inboxuri = $inboxes["inbox"];
$aprofile->sharedInboxuri = $inboxes["sharedInbox"];
$aprofile->insert ();
return $aprofile;
foreach ($profile as $key => $value) {
$aprofile->$key = $value;
}
/**
* Returns sharedInbox if possible, inbox otherwise
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return string Inbox URL
*/
public function get_inbox ()
{
if (is_null ($this->sharedInboxuri)) {
return $this->inboxuri;
}
return $aprofile;
}
return $this->sharedInboxuri;
/**
* Given an existent local profile creates an ActivityPub profile.
* One must be careful not to give a user profile to this function
* as only remote users have ActivityPub_profiles on local instance
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Profile $profile
* @return Activitypub_profile
*/
private static function create_from_local_profile(Profile $profile)
{
$url = $profile->getURL();
$inboxes = Activitypub_explorer::get_actor_inboxes_uri($url);
$aprofile->created = $aprofile->modified = common_sql_now();
$aprofile = new Activitypub_profile;
$aprofile->profile_id = $profile->getID();
$aprofile->uri = $url;
$aprofile->nickname = $profile->getNickname();
$aprofile->fullname = $profile->getFullname();
$aprofile->bio = substr($profile->getDescription(), 0, 1000);
$aprofile->inboxuri = $inboxes["inbox"];
$aprofile->sharedInboxuri = $inboxes["sharedInbox"];
$aprofile->insert();
return $aprofile;
}
/**
* Returns sharedInbox if possible, inbox otherwise
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return string Inbox URL
*/
public function get_inbox()
{
if (is_null($this->sharedInboxuri)) {
return $this->inboxuri;
}
/**
* Getter for uri property
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return string URI
*/
public function get_uri ()
{
return $this->uri;
return $this->sharedInboxuri;
}
/**
* Getter for uri property
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return string URI
*/
public function get_uri()
{
return $this->uri;
}
/**
* Ensures a valid Activitypub_profile when provided with a valid URI.
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $url
* @return Activitypub_profile
* @throws Exception if it isn't possible to return an Activitypub_profile
*/
public static function get_from_uri($url)
{
$explorer = new Activitypub_explorer();
$profiles_found = $explorer->lookup($url);
if (!empty($profiles_found)) {
return self::from_profile($profiles_found[0]);
} else {
throw new Exception('No valid ActivityPub profile found for given URI.');
}
}
/**
* Look up, and if necessary create, an Activitypub_profile for the remote
* entity with the given webfinger address.
* This should never return null -- you will either get an object or
* an exception will be thrown.
*
* @author GNU Social
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $addr webfinger address
* @return Activitypub_profile
* @throws Exception on error conditions
*/
public static function ensure_web_finger($addr)
{
// Normalize $addr, i.e. add 'acct:' if missing
$addr = Discovery::normalize($addr);
// Try the cache
$uri = self::cacheGet(sprintf('activitypub_profile:webfinger:%s', $addr));
if ($uri !== false) {
if (is_null($uri)) {
// Negative cache entry
// TRANS: Exception.
throw new Exception(_m('Not a valid webfinger address (via cache).'));
}
try {
return self::get_from_uri($uri);
} catch (Exception $e) {
common_log(LOG_ERR, sprintf(__METHOD__ . ': Webfinger address cache inconsistent with database, did not find Activitypub_profile uri==%s', $uri));
self::cacheSet(sprintf('activitypub_profile:webfinger:%s', $addr), false);
}
}
/**
* Ensures a valid Activitypub_profile when provided with a valid URI.
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $url
* @return Activitypub_profile
* @throws Exception if it isn't possible to return an Activitypub_profile
*/
public static function get_from_uri ($url)
{
$explorer = new Activitypub_explorer ();
$profiles_found = $explorer->lookup ($url);
if (!empty ($profiles_found)) {
return self::from_profile ($profiles_found[0]);
} else {
throw new Exception ('No valid ActivityPub profile found for given URI');
}
// If it doesn't return a valid Activitypub_profile an exception will
// have been thrown before getting to this point.
// Now, try some discovery
$disco = new Discovery();
try {
$xrd = $disco->lookup($addr);
} catch (Exception $e) {
// Save negative cache entry so we don't waste time looking it up again.
// @todo FIXME: Distinguish temporary failures?
self::cacheSet(sprintf('activitypub_profile:webfinger:%s', $addr), null);
// TRANS: Exception.
throw new Exception(_m('Not a valid webfinger address.'));
}
/**
* Look up, and if necessary create, an Activitypub_profile for the remote
* entity with the given webfinger address.
* This should never return null -- you will either get an object or
* an exception will be thrown.
*
* @author GNU Social
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $addr webfinger address
* @return Activitypub_profile
* @throws Exception on error conditions
*/
public static function ensure_web_finger ($addr)
{
// Normalize $addr, i.e. add 'acct:' if missing
$addr = Discovery::normalize ($addr);
$hints = array_merge(
array('webfinger' => $addr),
DiscoveryHints::fromXRD($xrd)
);
// Try the cache
$uri = self::cacheGet (sprintf ('activitypub_profile:webfinger:%s', $addr));
if ($uri !== false) {
if (is_null ($uri)) {
// Negative cache entry
// TRANS: Exception.
throw new Exception (_m ('Not a valid webfinger address (via cache).'));
}
try {
return self::get_from_uri ($uri);
} catch (Exception $e) {
common_log (LOG_ERR, sprintf (__METHOD__ . ': Webfinger address cache inconsistent with database, did not find Activitypub_profile uri==%s', $uri));
self::cacheSet (sprintf ('activitypub_profile:webfinger:%s', $addr), false);
}
}
// Now, try some discovery
$disco = new Discovery ();
try {
$xrd = $disco->lookup ($addr);
} catch (Exception $e) {
// Save negative cache entry so we don't waste time looking it up again.
// @todo FIXME: Distinguish temporary failures?
self::cacheSet (sprintf ('activitypub_profile:webfinger:%s', $addr), null);
// TRANS: Exception.
throw new Exception (_m ('Not a valid webfinger address.'));
}
$hints = array_merge (array ('webfinger' => $addr),
DiscoveryHints::fromXRD ($xrd));
// If there's an Hcard, let's grab its info
if (array_key_exists ('hcard', $hints)) {
if (!array_key_exists ('profileurl', $hints) ||
// If there's an Hcard, let's grab its info
if (array_key_exists('hcard', $hints)) {
if (!array_key_exists('profileurl', $hints) ||
$hints['hcard'] != $hints['profileurl']) {
$hcardHints = DiscoveryHints::fromHcardUrl ($hints['hcard']);
$hints = array_merge ($hcardHints, $hints);
}
}
$hcardHints = DiscoveryHints::fromHcardUrl($hints['hcard']);
$hints = array_merge($hcardHints, $hints);
}
}
// If we got a profile page, try that!
$profileUrl = null;
if (array_key_exists ('profileurl', $hints)) {
$profileUrl = $hints['profileurl'];
try {
common_log (LOG_INFO, "Discovery on acct:$addr with profile URL $profileUrl");
$aprofile = self::get_from_uri ($hints['profileurl']);
self::cacheSet (sprintf ('activitypub_profile:webfinger:%s', $addr), $aprofile->get_uri ());
return $aprofile;
} catch (Exception $e) {
common_log (LOG_WARNING, "Failed creating profile from profile URL '$profileUrl': " . $e->getMessage ());
// keep looking
// If we got a profile page, try that!
$profileUrl = null;
if (array_key_exists('profileurl', $hints)) {
$profileUrl = $hints['profileurl'];
try {
common_log(LOG_INFO, "Discovery on acct:$addr with profile URL $profileUrl");
$aprofile = self::get_from_uri($hints['profileurl']);
self::cacheSet(sprintf('activitypub_profile:webfinger:%s', $addr), $aprofile->get_uri());
return $aprofile;
} catch (Exception $e) {
common_log(LOG_WARNING, "Failed creating profile from profile URL '$profileUrl': " . $e->getMessage());
// keep looking
//
// @todo FIXME: This means an error discovering from profile page
// may give us a corrupt entry using the webfinger URI, which
// will obscure the correct page-keyed profile later on.
}
}
// XXX: try hcard
// XXX: try FOAF
// TRANS: Exception. %s is a webfinger address.
throw new Exception (sprintf (_m ('Could not find a valid profile for "%s".'), $addr));
}
}
// XXX: try hcard
// XXX: try FOAF
// TRANS: Exception. %s is a webfinger address.
throw new Exception(sprintf(_m('Could not find a valid profile for "%s".'), $addr));
}
}

View File

@ -0,0 +1,58 @@
<?php
/**
* GNU social - a federating social network
*
* ActivityPubPlugin implementation for GNU Social
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
* ActivityPub error representation
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://www.gnu.org/software/social/
*/
class Activitypub_reject extends Managed_DataObject
{
/**
* Generates an ActivityPub representation of a Reject
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param array $object
* @return pretty array to be used in a response
*/
public static function reject_to_array($object)
{
$res = array("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Reject",
"object" => $object
);
return $res;
}
}

View File

@ -25,8 +25,8 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
@ -40,16 +40,16 @@ if (!defined ('GNUSOCIAL')) {
*/
class Activitypub_tag extends Managed_DataObject
{
/**
* Generates a pretty tag from a Tag object
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Tag $tag
* @return pretty array to be used in a response
*/
public static function tag_to_array ($tag)
{
$res = [
/**
* Generates a pretty tag from a Tag object
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Tag $tag
* @return pretty array to be used in a response
*/
public static function tag_to_array($tag)
{
$res = [
'@context' => [
"https://www.w3.org/ns/activitystreams",
[
@ -57,9 +57,9 @@ class Activitypub_tag extends Managed_DataObject
]
],
'name' => $tag,
'url' => common_local_url ('tag', array('tag' => $tag))
'url' => common_local_url('tag', array('tag' => $tag))
];
return $res;
}
return $res;
}
}

View File

@ -0,0 +1,59 @@
<?php
/**
* GNU social - a federating social network
*
* ActivityPubPlugin implementation for GNU Social
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
* ActivityPub error representation
*
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://www.gnu.org/software/social/
*/
class Activitypub_undo extends Managed_DataObject
{
/**
* Generates an ActivityPub representation of a Undo
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param array $object
* @return pretty array to be used in a response
*/
public static function undo_to_array($object)
{
$res = array("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Undo",
"actor" => $object["actor"],
"object" => $object
);
return $res;
}
}

25
composer.json Normal file
View File

@ -0,0 +1,25 @@
{
"name": "dansup/activity-pub",
"description": "ActivityPub plugin for GNU/Social",
"type": "gnusocial-plugin",
"require": {},
"require-dev": {
"phpunit/phpunit": "^7.2"
},
"license": "AGPL",
"autoload": {
"psr-4": {
"Tests\\": "tests/"
}
},
"authors": [
{
"name": "Daniel Supernault",
"email": "danielsupernault@gmail.com"
},
{
"name": "Diogo Cordeiro",
"email": "diogo@fc.up.pt"
}
]
}

1423
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

25
phpunit.xml Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./app</directory>
</whitelist>
</filter>
</phpunit>

View File

@ -0,0 +1,28 @@
<?php
namespace Tests;
trait CreatesApplication
{
/**
* Creates the application.
*
* @return todo
*/
public static function createApplication()
{
if (!defined('INSTALLDIR')) {
define('INSTALLDIR', __DIR__ . '/../../../');
}
if (!defined('GNUSOCIAL')) {
define('GNUSOCIAL', true);
}
if (!defined('STATUSNET')) {
define('STATUSNET', true); // compatibility
}
require INSTALLDIR . '/lib/common.php';
return true;
}
}

15
tests/TestCase.php Normal file
View File

@ -0,0 +1,15 @@
<?php
namespace Tests;
use PHPUnit\Framework\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
protected function setUp()
{
$this->createApplication();
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Tests\Unit;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function testBasicTest()
{
$this->assertTrue(true);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Tests\Unit;
use Tests\TestCase;
class ProfileObjectTest extends TestCase
{
public function testLibraryInstalled()
{
$this->assertTrue(class_exists('\Activitypub_profile'));
}
public function testProfileObject()
{
// Mimic proper ACCEPT header
$_SERVER['HTTP_ACCEPT'] = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams';
// Fetch profile
$user = \Profile::getKV('id', 1);
// Fetch ActivityPub Actor Object representation
$profile = \Activitypub_profile::profile_to_array($user);
$this->assertTrue(is_array($profile));
$this->assertTrue(isset($profile['inbox']));
$this->assertTrue(isset($profile['outbox']));
}
}

View File

@ -24,12 +24,13 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
class DiscoveryHints {
static function fromXRD(XML_XRD $xrd)
class DiscoveryHints
{
public static function fromXRD(XML_XRD $xrd)
{
$hints = array();
@ -60,7 +61,7 @@ class DiscoveryHints {
return $hints;
}
static function fromHcardUrl($url)
public static function fromHcardUrl($url)
{
$client = new HTTPClient();
$client->setHeader('Accept', 'text/html,application/xhtml+xml');
@ -76,11 +77,13 @@ class DiscoveryHints {
return null;
}
return self::hcardHints($response->getBody(),
$response->getEffectiveUrl());
return self::hcardHints(
$response->getBody(),
$response->getEffectiveUrl()
);
}
static function hcardHints($body, $url)
public static function hcardHints($body, $url)
{
$hcard = self::_hcard($body, $url);
@ -119,7 +122,7 @@ class DiscoveryHints {
return $hints;
}
static function _hcard($body, $url)
public static function _hcard($body, $url)
{
$mf2 = new Mf2\Parser($body, $url);
$mf2 = $mf2->parse();

View File

@ -25,8 +25,8 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
@ -42,205 +42,215 @@ if (!defined ('GNUSOCIAL')) {
*/
class Activitypub_explorer
{
private $discovered_actor_profiles = array ();
private $discovered_actor_profiles = array();
/**
* Get every profile from the given URL
* This function cleans the $this->discovered_actor_profiles array
* so that there is no erroneous data
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $url User's url
* @return array of Profile objects
*/
public function lookup ($url)
{
$this->discovered_actor_profiles = array ();
/**
* Get every profile from the given URL
* This function cleans the $this->discovered_actor_profiles array
* so that there is no erroneous data
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $url User's url
* @return array of Profile objects
*/
public function lookup($url)
{
$this->discovered_actor_profiles = array();
return $this->_lookup ($url);
return $this->_lookup($url);
}
/**
* Get every profile from the given URL
* This is a recursive function that will accumulate the results on
* $discovered_actor_profiles array
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $url User's url
* @return array of Profile objects
*/
private function _lookup($url)
{
// First check if we already have it locally and, if so, return it
// If the local fetch fails: grab it remotely, store locally and return
if (! ($this->grab_local_user($url) || $this->grab_remote_user($url))) {
throw new Exception("User not found.");
}
/**
* Get every profile from the given URL
* This is a recursive function that will accumulate the results on
* $discovered_actor_profiles array
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $url User's url
* @return array of Profile objects
*/
private function _lookup ($url)
{
// First check if we already have it locally and, if so, return it
// If the local fetch fails: grab it remotely, store locally and return
if (! ($this->grab_local_user ($url) || $this->grab_remote_user ($url))) {
throw new Exception ("User not found");
}
return $this->discovered_actor_profiles;
}
return $this->discovered_actor_profiles;
/**
* This ensures that we are using a valid ActivityPub URI
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $url
* @return boolean success state (related to the response)
* @throws Exception (If the HTTP request fails)
*/
private function ensure_proper_remote_uri($url)
{
$client = new HTTPClient();
$headers = array();
$headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
$headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
$response = $client->get($url, $headers);
if (!$response->isOk()) {
throw new Exception("Invalid Actor URL.");
}
$res = json_decode($response->getBody(), JSON_UNESCAPED_SLASHES);
if (self::validate_remote_response($res)) {
$this->temp_res = $res;
return true;
}
/**
* Get a local user profiles from its URL and joins it on
* $this->discovered_actor_profiles
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $url User's url
* @return boolean success state
*/
private function grab_local_user ($url)
{
if (($actor_profile = self::get_profile_by_url ($url)) != false) {
$this->discovered_actor_profiles[]= $actor_profile;
return true;
} else {
/******************************** XXX: ********************************
* Sometimes it is not true that the user is not locally available, *
* mostly when it is a local user and URLs slightly changed *
* e.g.: GS instance owner changed from standard urls to pretty urls *
* (not sure if this is necessary, but anyway) *
**********************************************************************/
return false;
}
// Iff we really are in the same instance
$root_url_len = strlen (common_root_url ());
if (substr ($url, 0, $root_url_len) == common_root_url ()) {
// Grab the nickname and try to get the user
if (($actor_profile = Profile::getKV ("nickname", substr ($url, $root_url_len))) != false) {
$this->discovered_actor_profiles[]= $actor_profile;
return true;
}
}
}
return false;
/**
* Get a local user profiles from its URL and joins it on
* $this->discovered_actor_profiles
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $uri Actor's uri
* @return boolean success state
*/
private function grab_local_user($uri)
{
// Ensure proper remote URI
// If an exceptiong ocurrs here it's better to just leave everything
// break than to continue processing
if ($this->ensure_proper_remote_uri($uri)) {
$uri = $this->temp_res["id"];
}
try {
// Try standard ActivityPub route
$aprofile = Activitypub_profile::getKV("uri", $uri);
if ($aprofile instanceof Activitypub_profile) {
$profile = $aprofile->local_profile();
} else {
// This potential local user is not a remote user.
// Let's check for pure blood!
$profile = User::getByNickname($this->temp_res["preferredUsername"])->getProfile();
}
// We found something!
$this->discovered_actor_profiles[]= $profile;
unset($this->temp_res); // IMPORTANT to avoid _dangerous_ noise in the Explorer system
return true;
} catch (Exception $e) {
// We can safely ignore every exception here as we are return false
// when it fails the lookup for existing local representation
}
/**
* Get a remote user(s) profile(s) from its URL and joins it on
* $this->discovered_actor_profiles
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $url User's url
* @return boolean success state
*/
private function grab_remote_user ($url)
{
$client = new HTTPClient ();
$headers = array();
$headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
$headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
$response = $client->get ($url, $headers);
if (!$response->isOk ()) {
throw new Exception ("Invalid Actor URL.");
}
$res = json_decode ($response->getBody (), JSON_UNESCAPED_SLASHES);
if (isset ($res["orderedItems"])) { // It's a potential collection of actors!!!
foreach ($res["orderedItems"] as $profile) {
if ($this->_lookup ($profile) == false) {
// XXX: Invalid actor found, not sure how we handle those
}
}
// Go through entire collection
if (!is_null ($res["next"])) {
$this->_lookup ($res["next"]);
}
return true;
} else if (self::validate_remote_response ($res)) {
$this->discovered_actor_profiles[]= $this->store_profile ($res);
return true;
}
return false;
}
return false;
/**
* Get a remote user(s) profile(s) from its URL and joins it on
* $this->discovered_actor_profiles
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $url User's url
* @return boolean success state
*/
private function grab_remote_user($url)
{
if (!isset($this->temp_res)) {
$client = new HTTPClient();
$headers = array();
$headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
$headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
$response = $client->get($url, $headers);
if (!$response->isOk()) {
throw new Exception("Invalid Actor URL.");
}
$res = json_decode($response->getBody(), JSON_UNESCAPED_SLASHES);
} else {
$res = $this->temp_res;
unset($this->temp_res);
}
if (isset($res["orderedItems"])) { // It's a potential collection of actors!!!
foreach ($res["orderedItems"] as $profile) {
if ($this->_lookup($profile) == false) {
// XXX: Invalid actor found, not sure how we handle those
}
}
// Go through entire collection
if (!is_null($res["next"])) {
$this->_lookup($res["next"]);
}
return true;
} elseif (self::validate_remote_response($res)) {
$this->discovered_actor_profiles[]= $this->store_profile($res);
return true;
}
/**
* Save remote user profile in local instance
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param array $res remote response
* @return Profile remote Profile object
*/
private function store_profile ($res)
{
$aprofile = new Activitypub_profile;
$aprofile->uri = $res["url"];
$aprofile->nickname = $res["nickname"];
$aprofile->fullname = $res["display_name"];
$aprofile->bio = substr ($res["summary"], 0, 1000);
$aprofile->inboxuri = $res["inbox"];
$aprofile->sharedInboxuri = isset ($res["sharedInbox"]) ? $res["sharedInbox"] : $res["inbox"];
return false;
}
$aprofile->do_insert ();
/**
* Save remote user profile in local instance
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param array $res remote response
* @return Profile remote Profile object
*/
private function store_profile($res)
{
$aprofile = new Activitypub_profile;
$aprofile->uri = $res["id"];
$aprofile->nickname = $res["preferredUsername"];
$aprofile->fullname = $res["name"];
$aprofile->bio = substr($res["summary"], 0, 1000);
$aprofile->inboxuri = $res["inbox"];
$aprofile->sharedInboxuri = isset($res["sharedInbox"]) ? $res["sharedInbox"] : $res["inbox"];
return $aprofile->local_profile ();
$aprofile->do_insert();
return $aprofile->local_profile();
}
/**
* Validates a remote response in order to determine whether this
* response is a valid profile or not
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param array $res remote response
* @return boolean success state
*/
private static function validate_remote_response($res)
{
if (!isset($res["id"], $res["preferredUsername"], $res["name"], $res["summary"], $res["inbox"])) {
return false;
}
/**
* Validates a remote response in order to determine whether this
* response is a valid profile or not
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param array $res remote response
* @return boolean success state
*/
private static function validate_remote_response ($res)
{
if (!isset ($res["url"], $res["nickname"], $res["display_name"], $res["summary"], $res["inbox"])) {
return false;
}
return true;
}
return true;
/**
* Given a valid actor profile url returns its inboxes
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $url of Actor profile
* @return boolean|array false if fails | array with inbox and shared inbox if successful
*/
public static function get_actor_inboxes_uri($url)
{
$client = new HTTPClient();
$headers = array();
$headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
$headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
$response = $client->get($url, $headers);
if (!$response->isOk()) {
throw new Exception("Invalid Actor URL.");
}
$res = json_decode($response->getBody(), JSON_UNESCAPED_SLASHES);
if (self::validate_remote_response($res)) {
return array("inbox" => $res["inbox"],
"sharedInbox" => isset($res["sharedInbox"]) ? $res["sharedInbox"] : $res["inbox"]);
}
/**
* Get a profile from it's profileurl
* Unfortunately GNU Social cache is not truly reliable when handling
* potential ActivityPub remote profiles, as so it is important to use
* this hacky workaround (at least for now)
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $v URL
* @return boolean|Profile false if fails | Profile object if successful
*/
public static function get_profile_by_url ($v)
{
$i = Managed_DataObject::getcached("Profile", "profileurl", $v);
if (empty ($i)) { // false = cache miss
$i = new Profile;
$result = $i->get ("profileurl", $v);
if ($result) {
// Hit!
$i->encache();
} else {
return false;
}
}
return $i;
}
/**
* Given a valid actor profile url returns its inboxes
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $url of Actor profile
* @return boolean|array false if fails | array with inbox and shared inbox if successful
*/
public static function get_actor_inboxes_uri ($url)
{
$client = new HTTPClient ();
$headers = array();
$headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
$headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
$response = $client->get ($url, $headers);
if (!$response->isOk ()) {
throw new Exception ("Invalid Actor URL.");
}
$res = json_decode ($response->getBody (), JSON_UNESCAPED_SLASHES);
if (self::validate_remote_response ($res)) {
return array ("inbox" => $res["inbox"],
"sharedInbox" => isset ($res["sharedInbox"]) ? $res["sharedInbox"] : $res["inbox"]);
}
return false;
}
return false;
}
}

View File

@ -25,8 +25,8 @@
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
if (!defined ('GNUSOCIAL')) {
exit (1);
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
@ -43,184 +43,202 @@ if (!defined ('GNUSOCIAL')) {
*/
class Activitypub_postman
{
private $actor;
private $to = array ();
private $client;
private $headers;
private $actor;
private $to = [];
private $client;
private $headers;
/**
* Create a postman to deliver something to someone
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Profile of sender
* @param Activitypub_profile $to array of destinataries
*/
public function __construct ($from, $to = array ())
{
$this->client = new HTTPClient ();
$this->actor = $from;
$this->to = $to;
$this->headers = array();
$this->headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
$this->headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
/**
* Create a postman to deliver something to someone
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Profile of sender
* @param Activitypub_profile $to array of destinataries
*/
public function __construct($from, $to = [])
{
$this->client = new HTTPClient();
$this->actor = $from;
$this->to = $to;
$this->headers = [];
$this->headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
$this->headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
}
/**
* Send a follow notification to remote instance
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @throws Exception
*/
public function follow()
{
$data = Activitypub_follow::follow_to_array($this->actor->getUrl(), $this->to[0]->getUrl());
$this->client->setBody(json_encode($data));
$res = $this->client->post($this->to[0]->get_inbox(), $this->headers);
$res_body = json_decode($res->getBody());
if ($res->isOk() || $res->getStatus() == 409) {
$pending_list = new Activitypub_pending_follow_requests($this->actor->getID(), $this->to[0]->getID());
if (! ($res->getStatus() == 409 || $res_body->type == "Accept")) {
$pending_list->add();
throw new Exception("Your follow request is pending acceptation.");
}
$pending_list->remove();
return true;
} elseif (isset($res_body[0]->error)) {
throw new Exception($res_body[0]->error);
}
/**
* Send a follow notification to remote instance
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
*/
public function follow ()
{
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Follow",
"actor" => $this->actor->getUrl (),
"object" => $this->to[0]->getUrl ());
$this->client->setBody (json_encode ($data));
$this->client->post ($this->to[0]->get_inbox (), $this->headers);
}
throw new Exception("An unknown error occurred.");
}
/**
* Send a Undo Follow notification to remote instance
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
*/
public function undo_follow ()
{
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Undo",
"actor" => $this->actor->getUrl (),
"object" => array (
"type" => "Follow",
"object" => $this->to[0]->getUrl ()
)
/**
* Send a Undo Follow notification to remote instance
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
*/
public function undo_follow()
{
$data = Activitypub_undo::undo_to_array(
Activitypub_follow::follow_to_array(
$this->actor->getUrl(),
$this->to[0]->getUrl()
)
);
$this->client->setBody (json_encode ($data));
$this->client->post ($this->to[0]->get_inbox (), $this->headers);
}
$this->client->setBody(json_encode($data));
$res = $this->client->post($this->to[0]->get_inbox(), $this->headers);
$res_body = json_decode($res->getBody());
/**
* Send a Like notification to remote instances holding the notice
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Notice $notice
*/
public function like ($notice)
{
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Like",
"actor" => $this->actor->getUrl (),
"object" => $notice->getUri ());
$this->client->setBody (json_encode ($data));
foreach ($this->to_inbox () as $inbox) {
$this->client->post ($inbox, $this->headers);
}
if ($res->isOk() || $res->getStatus() == 409) {
$pending_list = new Activitypub_pending_follow_requests($this->actor->getID(), $this->to[0]->getID());
$pending_list->remove();
return true;
}
if (isset($res_body[0]->error)) {
throw new Exception($res_body[0]->error);
}
throw new Exception("An unknown error occurred.");
}
/**
* Send a Undo Like notification to remote instances holding the notice
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Notice $notice
*/
public function undo_like ($notice)
{
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Undo",
"actor" => $this->actor->getUrl (),
"object" => array (
"type" => "Like",
"object" => $notice->getUri ()
)
/**
* Send a Like notification to remote instances holding the notice
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Notice $notice
*/
public function like($notice)
{
$data = Activitypub_like::like_to_array(
$this->actor->getUrl(),
Activitypub_notice::notice_to_array($notice)
);
$this->client->setBody (json_encode ($data));
foreach ($this->to_inbox () as $inbox) {
$this->client->post ($inbox, $this->headers);
}
$this->client->setBody(json_encode($data));
foreach ($this->to_inbox() as $inbox) {
$this->client->post($inbox, $this->headers);
}
}
/**
* Send a Announce notification to remote instances
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Notice $notice
*/
public function announce ($notice)
{
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
"id" => $notice->getUri (),
"url" => $notice->getUrl (),
"type" => "Announce",
"actor" => $this->actor->getUrl (),
"to" => "https://www.w3.org/ns/activitystreams#Public",
"object" => $notice->getUri ()
);
$this->client->setBody (json_encode ($data));
foreach ($this->to_inbox () as $inbox) {
$this->client->post ($inbox, $this->headers);
}
}
/**
* Send a Create notification to remote instances
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Notice $notice
*/
public function create ($notice)
{
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
"id" => $notice->getUri (),
"type" => "Create",
"actor" => $this->actor->getUrl (),
"to" => "https://www.w3.org/ns/activitystreams#Public",
"object" => array (
"type" => "Note",
"url" => $notice->getUrl (),
"content" => $notice->getContent ()
)
);
if (isset ($notice->reply_to)) {
$data["object"]["reply_to"] = $notice->getParent ()->getUri ();
}
$this->client->setBody (json_encode ($data));
foreach ($this->to_inbox () as $inbox) {
$this->client->post ($inbox, $this->headers);
}
}
/**
* Send a Delete notification to remote instances holding the notice
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Notice $notice
*/
public function delete ($notice)
{
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Delete",
"actor" => $this->actor->getUrl (),
"object" => $notice->getUri ()
/**
* Send a Undo Like notification to remote instances holding the notice
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Notice $notice
*/
public function undo_like($notice)
{
$data = Activitypub_undo::undo_to_array(
Activitypub_like::like_to_array(
$this->actor->getUrl(),
Activitypub_notice::notice_to_array($notice)
)
);
$this->client->setBody (json_encode ($data));
foreach ($this->to_inbox () as $inbox) {
$this->client->post ($inbox, $this->headers);
$this->client->setBody(json_encode($data));
foreach ($this->to_inbox() as $inbox) {
$this->client->post($inbox, $this->headers);
}
}
/**
* Send a Create notification to remote instances
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Notice $notice
*/
public function create($notice)
{
$data = Activitypub_create::create_to_array(
$notice->getUri(),
$this->actor->getUrl(),
Activitypub_notice::notice_to_array($notice)
);
if (isset($notice->reply_to)) {
$data["object"]["reply_to"] = $notice->getParent()->getUri();
}
$this->client->setBody(json_encode($data));
foreach ($this->to_inbox() as $inbox) {
$this->client->post($inbox, $this->headers);
}
}
/**
* Send a Announce notification to remote instances
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Notice $notice
*/
public function announce($notice)
{
$data = Activitypub_announce::announce_to_array(
$this->actor->getUrl(),
Activitypub_notice::notice_to_array($notice)
);
$this->client->setBody(json_encode($data));
foreach ($this->to_inbox() as $inbox) {
$this->client->post($inbox, $this->headers);
}
}
/**
* Send a Delete notification to remote instances holding the notice
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Notice $notice
*/
public function delete($notice)
{
$data = Activitypub_delete::delete_to_array(Activitypub_notice::notice_to_array($notice));
$this->client->setBody(json_encode($data));
$errors = array();
foreach ($this->to_inbox() as $inbox) {
$res = $this->client->post($inbox, $this->headers);
if (!$res->isOk()) {
$res_body = json_decode($res->getBody());
if (isset($res_body[0]->error)) {
$errors[] = ($res_body[0]->error);
continue;
}
$errors[] = ("An unknown error occurred.");
}
}
if (!empty($errors)) {
throw new Exception(json_encode($errors));
}
}
/**
* Clean list of inboxes to deliver messages
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return array To Inbox URLs
*/
private function to_inbox()
{
$to_inboxes = array();
foreach ($this->to as $to_profile) {
$to_inboxes[] = $to_profile->get_inbox();
}
/**
* Clean list of inboxes to deliver messages
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return array To Inbox URLs
*/
private function to_inbox ()
{
$to_inboxes = array ();
foreach ($this->to as $to_profile) {
$to_inboxes[] = $to_profile->get_inbox ();
}
return array_unique ($to_inboxes);
}
return array_unique($to_inboxes);
}
}