]> git.mxchange.org Git - friendica.git/blob - src/Protocol/ActivityPub/Transmitter.php
7155d83094cb38a400dec01d266143cda40698c6
[friendica.git] / src / Protocol / ActivityPub / Transmitter.php
1 <?php
2 /**
3  * @file src/Protocol/ActivityPub/Transmitter.php
4  */
5 namespace Friendica\Protocol\ActivityPub;
6
7 use Friendica\Database\DBA;
8 use Friendica\Core\System;
9 use Friendica\BaseObject;
10 use Friendica\Util\HTTPSignature;
11 use Friendica\Core\Protocol;
12 use Friendica\Model\Conversation;
13 use Friendica\Model\Contact;
14 use Friendica\Model\APContact;
15 use Friendica\Model\Item;
16 use Friendica\Model\Term;
17 use Friendica\Model\User;
18 use Friendica\Util\DateTimeFormat;
19 use Friendica\Content\Text\BBCode;
20 use Friendica\Util\JsonLD;
21 use Friendica\Util\LDSignature;
22 use Friendica\Protocol\ActivityPub;
23 use Friendica\Model\Profile;
24
25 /**
26  * @brief ActivityPub Transmitter Protocol class
27  */
28 class Transmitter
29 {
30         /**
31          * @brief collects the lost of followers of the given owner
32          *
33          * @param array $owner Owner array
34          * @param integer $page Page number
35          *
36          * @return array of owners
37          */
38         public static function getFollowers($owner, $page = null)
39         {
40                 $condition = ['rel' => [Contact::FOLLOWER, Contact::FRIEND], 'network' => Protocol::NATIVE_SUPPORT, 'uid' => $owner['uid'],
41                         'self' => false, 'hidden' => false, 'archive' => false, 'pending' => false];
42                 $count = DBA::count('contact', $condition);
43
44                 $data = ['@context' => ActivityPub::CONTEXT];
45                 $data['id'] = System::baseUrl() . '/followers/' . $owner['nickname'];
46                 $data['type'] = 'OrderedCollection';
47                 $data['totalItems'] = $count;
48
49                 // When we hide our friends we will only show the pure number but don't allow more.
50                 $profile = Profile::getByUID($owner['uid']);
51                 if (!empty($profile['hide-friends'])) {
52                         return $data;
53                 }
54
55                 if (empty($page)) {
56                         $data['first'] = System::baseUrl() . '/followers/' . $owner['nickname'] . '?page=1';
57                 } else {
58                         $list = [];
59
60                         $contacts = DBA::select('contact', ['url'], $condition, ['limit' => [($page - 1) * 100, 100]]);
61                         while ($contact = DBA::fetch($contacts)) {
62                                 $list[] = $contact['url'];
63                         }
64
65                         if (!empty($list)) {
66                                 $data['next'] = System::baseUrl() . '/followers/' . $owner['nickname'] . '?page=' . ($page + 1);
67                         }
68
69                         $data['partOf'] = System::baseUrl() . '/followers/' . $owner['nickname'];
70
71                         $data['orderedItems'] = $list;
72                 }
73
74                 return $data;
75         }
76
77         /**
78          * @brief Create list of following contacts
79          *
80          * @param array $owner Owner array
81          * @param integer $page Page numbe
82          *
83          * @return array of following contacts
84          */
85         public static function getFollowing($owner, $page = null)
86         {
87                 $condition = ['rel' => [Contact::SHARING, Contact::FRIEND], 'network' => Protocol::NATIVE_SUPPORT, 'uid' => $owner['uid'],
88                         'self' => false, 'hidden' => false, 'archive' => false, 'pending' => false];
89                 $count = DBA::count('contact', $condition);
90
91                 $data = ['@context' => ActivityPub::CONTEXT];
92                 $data['id'] = System::baseUrl() . '/following/' . $owner['nickname'];
93                 $data['type'] = 'OrderedCollection';
94                 $data['totalItems'] = $count;
95
96                 // When we hide our friends we will only show the pure number but don't allow more.
97                 $profile = Profile::getByUID($owner['uid']);
98                 if (!empty($profile['hide-friends'])) {
99                         return $data;
100                 }
101
102                 if (empty($page)) {
103                         $data['first'] = System::baseUrl() . '/following/' . $owner['nickname'] . '?page=1';
104                 } else {
105                         $list = [];
106
107                         $contacts = DBA::select('contact', ['url'], $condition, ['limit' => [($page - 1) * 100, 100]]);
108                         while ($contact = DBA::fetch($contacts)) {
109                                 $list[] = $contact['url'];
110                         }
111
112                         if (!empty($list)) {
113                                 $data['next'] = System::baseUrl() . '/following/' . $owner['nickname'] . '?page=' . ($page + 1);
114                         }
115
116                         $data['partOf'] = System::baseUrl() . '/following/' . $owner['nickname'];
117
118                         $data['orderedItems'] = $list;
119                 }
120
121                 return $data;
122         }
123
124         /**
125          * @brief Public posts for the given owner
126          *
127          * @param array $owner Owner array
128          * @param integer $page Page numbe
129          *
130          * @return array of posts
131          */
132         public static function getOutbox($owner, $page = null)
133         {
134                 $public_contact = Contact::getIdForURL($owner['url'], 0, true);
135
136                 $condition = ['uid' => $owner['uid'], 'contact-id' => $owner['id'], 'author-id' => $public_contact,
137                         'wall' => true, 'private' => false, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT],
138                         'deleted' => false, 'visible' => true];
139                 $count = DBA::count('item', $condition);
140
141                 $data = ['@context' => ActivityPub::CONTEXT];
142                 $data['id'] = System::baseUrl() . '/outbox/' . $owner['nickname'];
143                 $data['type'] = 'OrderedCollection';
144                 $data['totalItems'] = $count;
145
146                 if (empty($page)) {
147                         $data['first'] = System::baseUrl() . '/outbox/' . $owner['nickname'] . '?page=1';
148                 } else {
149                         $list = [];
150
151                         $condition['parent-network'] = Protocol::NATIVE_SUPPORT;
152
153                         $items = Item::select(['id'], $condition, ['limit' => [($page - 1) * 20, 20], 'order' => ['created' => true]]);
154                         while ($item = Item::fetch($items)) {
155                                 $object = self::createObjectFromItemID($item['id']);
156                                 unset($object['@context']);
157                                 $list[] = $object;
158                         }
159
160                         if (!empty($list)) {
161                                 $data['next'] = System::baseUrl() . '/outbox/' . $owner['nickname'] . '?page=' . ($page + 1);
162                         }
163
164                         $data['partOf'] = System::baseUrl() . '/outbox/' . $owner['nickname'];
165
166                         $data['orderedItems'] = $list;
167                 }
168
169                 return $data;
170         }
171
172         /**
173          * Return the ActivityPub profile of the given user
174          *
175          * @param integer $uid User ID
176          * @return profile array
177          */
178         public static function profile($uid)
179         {
180                 $condition = ['uid' => $uid, 'blocked' => false, 'account_expired' => false,
181                         'account_removed' => false, 'verified' => true];
182                 $fields = ['guid', 'nickname', 'pubkey', 'account-type', 'page-flags'];
183                 $user = DBA::selectFirst('user', $fields, $condition);
184                 if (!DBA::isResult($user)) {
185                         return [];
186                 }
187
188                 $fields = ['locality', 'region', 'country-name'];
189                 $profile = DBA::selectFirst('profile', $fields, ['uid' => $uid, 'is-default' => true]);
190                 if (!DBA::isResult($profile)) {
191                         return [];
192                 }
193
194                 $fields = ['name', 'url', 'location', 'about', 'avatar'];
195                 $contact = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
196                 if (!DBA::isResult($contact)) {
197                         return [];
198                 }
199
200                 $data = ['@context' => ActivityPub::CONTEXT];
201                 $data['id'] = $contact['url'];
202                 $data['diaspora:guid'] = $user['guid'];
203                 $data['type'] = ActivityPub::ACCOUNT_TYPES[$user['account-type']];
204                 $data['following'] = System::baseUrl() . '/following/' . $user['nickname'];
205                 $data['followers'] = System::baseUrl() . '/followers/' . $user['nickname'];
206                 $data['inbox'] = System::baseUrl() . '/inbox/' . $user['nickname'];
207                 $data['outbox'] = System::baseUrl() . '/outbox/' . $user['nickname'];
208                 $data['preferredUsername'] = $user['nickname'];
209                 $data['name'] = $contact['name'];
210                 $data['vcard:hasAddress'] = ['@type' => 'vcard:Home', 'vcard:country-name' => $profile['country-name'],
211                         'vcard:region' => $profile['region'], 'vcard:locality' => $profile['locality']];
212                 $data['summary'] = $contact['about'];
213                 $data['url'] = $contact['url'];
214                 $data['manuallyApprovesFollowers'] = in_array($user['page-flags'], [Contact::PAGE_NORMAL, Contact::PAGE_PRVGROUP]);
215                 $data['publicKey'] = ['id' => $contact['url'] . '#main-key',
216                         'owner' => $contact['url'],
217                         'publicKeyPem' => $user['pubkey']];
218                 $data['endpoints'] = ['sharedInbox' => System::baseUrl() . '/inbox'];
219                 $data['icon'] = ['type' => 'Image',
220                         'url' => $contact['avatar']];
221
222                 // tags: https://kitty.town/@inmysocks/100656097926961126.json
223                 return $data;
224         }
225
226         /**
227          * @brief Returns an array with permissions of a given item array
228          *
229          * @param array $item
230          *
231          * @return array with permissions
232          */
233         private static function fetchPermissionBlockFromConversation($item)
234         {
235                 if (empty($item['thr-parent'])) {
236                         return [];
237                 }
238
239                 $condition = ['item-uri' => $item['thr-parent'], 'protocol' => Conversation::PARCEL_ACTIVITYPUB];
240                 $conversation = DBA::selectFirst('conversation', ['source'], $condition);
241                 if (!DBA::isResult($conversation)) {
242                         return [];
243                 }
244
245                 $activity = json_decode($conversation['source'], true);
246
247                 $actor = JsonLD::fetchElement($activity, 'actor', 'id');
248                 $profile = APContact::getByURL($actor);
249
250                 $item_profile = APContact::getByURL($item['author-link']);
251                 $exclude[] = $item['author-link'];
252
253                 if ($item['gravity'] == GRAVITY_PARENT) {
254                         $exclude[] = $item['owner-link'];
255                 }
256
257                 $permissions['to'][] = $actor;
258
259                 foreach (['to', 'cc', 'bto', 'bcc'] as $element) {
260                         if (empty($activity[$element])) {
261                                 continue;
262                         }
263                         if (is_string($activity[$element])) {
264                                 $activity[$element] = [$activity[$element]];
265                         }
266
267                         foreach ($activity[$element] as $receiver) {
268                                 if ($receiver == $profile['followers'] && !empty($item_profile['followers'])) {
269                                         $receiver = $item_profile['followers'];
270                                 }
271                                 if (!in_array($receiver, $exclude)) {
272                                         $permissions[$element][] = $receiver;
273                                 }
274                         }
275                 }
276                 return $permissions;
277         }
278
279         /**
280          * @brief Creates an array of permissions from an item thread
281          *
282          * @param array $item
283          *
284          * @return permission array
285          */
286         public static function createPermissionBlockForItem($item)
287         {
288                 $data = ['to' => [], 'cc' => []];
289
290                 $data = array_merge($data, self::fetchPermissionBlockFromConversation($item));
291
292                 $actor_profile = APContact::getByURL($item['author-link']);
293
294                 $terms = Term::tagArrayFromItemId($item['id'], TERM_MENTION);
295
296                 $contacts[$item['author-link']] = $item['author-link'];
297
298                 if (!$item['private']) {
299                         $data['to'][] = ActivityPub::PUBLIC_COLLECTION;
300                         if (!empty($actor_profile['followers'])) {
301                                 $data['cc'][] = $actor_profile['followers'];
302                         }
303
304                         foreach ($terms as $term) {
305                                 $profile = APContact::getByURL($term['url'], false);
306                                 if (!empty($profile) && empty($contacts[$profile['url']])) {
307                                         $data['cc'][] = $profile['url'];
308                                         $contacts[$profile['url']] = $profile['url'];
309                                 }
310                         }
311                 } else {
312                         $receiver_list = Item::enumeratePermissions($item);
313
314                         $mentioned = [];
315
316                         foreach ($terms as $term) {
317                                 $cid = Contact::getIdForURL($term['url'], $item['uid']);
318                                 if (!empty($cid) && in_array($cid, $receiver_list)) {
319                                         $contact = DBA::selectFirst('contact', ['url'], ['id' => $cid, 'network' => Protocol::ACTIVITYPUB]);
320                                         $data['to'][] = $contact['url'];
321                                         $contacts[$contact['url']] = $contact['url'];
322                                 }
323                         }
324
325                         foreach ($receiver_list as $receiver) {
326                                 $contact = DBA::selectFirst('contact', ['url'], ['id' => $receiver, 'network' => Protocol::ACTIVITYPUB]);
327                                 if (empty($contacts[$contact['url']])) {
328                                         $data['cc'][] = $contact['url'];
329                                         $contacts[$contact['url']] = $contact['url'];
330                                 }
331                         }
332                 }
333
334                 $parents = Item::select(['id', 'author-link', 'owner-link', 'gravity'], ['parent' => $item['parent']]);
335                 while ($parent = Item::fetch($parents)) {
336                         // Don't include data from future posts
337                         if ($parent['id'] >= $item['id']) {
338                                 continue;
339                         }
340
341                         $profile = APContact::getByURL($parent['author-link'], false);
342                         if (!empty($profile) && empty($contacts[$profile['url']])) {
343                                 $data['cc'][] = $profile['url'];
344                                 $contacts[$profile['url']] = $profile['url'];
345                         }
346
347                         if ($item['gravity'] != GRAVITY_PARENT) {
348                                 continue;
349                         }
350
351                         $profile = APContact::getByURL($parent['owner-link'], false);
352                         if (!empty($profile) && empty($contacts[$profile['url']])) {
353                                 $data['cc'][] = $profile['url'];
354                                 $contacts[$profile['url']] = $profile['url'];
355                         }
356                 }
357                 DBA::close($parents);
358
359                 if (empty($data['to'])) {
360                         $data['to'] = $data['cc'];
361                         $data['cc'] = [];
362                 }
363
364                 return $data;
365         }
366
367         /**
368          * @brief Fetches a list of inboxes of followers of a given user
369          *
370          * @param integer $uid User ID
371          *
372          * @return array of follower inboxes
373          */
374         public static function fetchTargetInboxesforUser($uid)
375         {
376                 $inboxes = [];
377
378                 $condition = ['uid' => $uid, 'network' => Protocol::ACTIVITYPUB, 'archive' => false, 'pending' => false];
379
380                 if (!empty($uid)) {
381                         $condition['rel'] = [Contact::FOLLOWER, Contact::FRIEND];
382                 }
383
384                 $contacts = DBA::select('contact', ['notify', 'batch'], $condition);
385                 while ($contact = DBA::fetch($contacts)) {
386                         $contact = defaults($contact, 'batch', $contact['notify']);
387                         $inboxes[$contact] = $contact;
388                 }
389                 DBA::close($contacts);
390
391                 return $inboxes;
392         }
393
394         /**
395          * @brief Fetches an array of inboxes for the given item and user
396          *
397          * @param array $item
398          * @param integer $uid User ID
399          *
400          * @return array with inboxes
401          */
402         public static function fetchTargetInboxes($item, $uid)
403         {
404                 $permissions = self::createPermissionBlockForItem($item);
405                 if (empty($permissions)) {
406                         return [];
407                 }
408
409                 $inboxes = [];
410
411                 if ($item['gravity'] == GRAVITY_ACTIVITY) {
412                         $item_profile = APContact::getByURL($item['author-link']);
413                 } else {
414                         $item_profile = APContact::getByURL($item['owner-link']);
415                 }
416
417                 foreach (['to', 'cc', 'bto', 'bcc'] as $element) {
418                         if (empty($permissions[$element])) {
419                                 continue;
420                         }
421
422                         foreach ($permissions[$element] as $receiver) {
423                                 if ($receiver == $item_profile['followers']) {
424                                         $inboxes = self::fetchTargetInboxesforUser($uid);
425                                 } else {
426                                         $profile = APContact::getByURL($receiver);
427                                         if (!empty($profile)) {
428                                                 $target = defaults($profile, 'sharedinbox', $profile['inbox']);
429                                                 $inboxes[$target] = $target;
430                                         }
431                                 }
432                         }
433                 }
434
435                 return $inboxes;
436         }
437
438         /**
439          * @brief Returns the activity type of a given item
440          *
441          * @param array $item
442          *
443          * @return activity type
444          */
445         public static function getTypeOfItem($item)
446         {
447                 if ($item['verb'] == ACTIVITY_POST) {
448                         if ($item['created'] == $item['edited']) {
449                                 $type = 'Create';
450                         } else {
451                                 $type = 'Update';
452                         }
453                 } elseif ($item['verb'] == ACTIVITY_LIKE) {
454                         $type = 'Like';
455                 } elseif ($item['verb'] == ACTIVITY_DISLIKE) {
456                         $type = 'Dislike';
457                 } elseif ($item['verb'] == ACTIVITY_ATTEND) {
458                         $type = 'Accept';
459                 } elseif ($item['verb'] == ACTIVITY_ATTENDNO) {
460                         $type = 'Reject';
461                 } elseif ($item['verb'] == ACTIVITY_ATTENDMAYBE) {
462                         $type = 'TentativeAccept';
463                 } else {
464                         $type = '';
465                 }
466
467                 return $type;
468         }
469
470         /**
471          * @brief Creates an activity array for a given item id
472          *
473          * @param integer $item_id
474          * @param boolean $object_mode Is the activity item is used inside another object?
475          *
476          * @return array of activity
477          */
478         public static function createActivityFromItem($item_id, $object_mode = false)
479         {
480                 $item = Item::selectFirst([], ['id' => $item_id, 'parent-network' => Protocol::NATIVE_SUPPORT]);
481
482                 if (!DBA::isResult($item)) {
483                         return false;
484                 }
485
486                 $condition = ['item-uri' => $item['uri'], 'protocol' => Conversation::PARCEL_ACTIVITYPUB];
487                 $conversation = DBA::selectFirst('conversation', ['source'], $condition);
488                 if (DBA::isResult($conversation)) {
489                         $data = json_decode($conversation['source']);
490                         if (!empty($data)) {
491                                 return $data;
492                         }
493                 }
494
495                 $type = self::getTypeOfItem($item);
496
497                 if (!$object_mode) {
498                         $data = ['@context' => ActivityPub::CONTEXT];
499
500                         if ($item['deleted'] && ($item['gravity'] == GRAVITY_ACTIVITY)) {
501                                 $type = 'Undo';
502                         } elseif ($item['deleted']) {
503                                 $type = 'Delete';
504                         }
505                 } else {
506                         $data = [];
507                 }
508
509                 $data['id'] = $item['uri'] . '#' . $type;
510                 $data['type'] = $type;
511                 $data['actor'] = $item['author-link'];
512
513                 $data['published'] = DateTimeFormat::utc($item["created"]."+00:00", DateTimeFormat::ATOM);
514
515                 if ($item["created"] != $item["edited"]) {
516                         $data['updated'] = DateTimeFormat::utc($item["edited"]."+00:00", DateTimeFormat::ATOM);
517                 }
518
519                 $data['context'] = self::fetchContextURLForItem($item);
520
521                 $data = array_merge($data, self::createPermissionBlockForItem($item));
522
523                 if (in_array($data['type'], ['Create', 'Update', 'Announce', 'Delete'])) {
524                         $data['object'] = self::createNote($item);
525                 } elseif ($data['type'] == 'Undo') {
526                         $data['object'] = self::createActivityFromItem($item_id, true);
527                 } else {
528                         $data['object'] = $item['thr-parent'];
529                 }
530
531                 $owner = User::getOwnerDataById($item['uid']);
532
533                 if (!$object_mode) {
534                         return LDSignature::sign($data, $owner);
535                 } else {
536                         return $data;
537                 }
538         }
539
540         /**
541          * @brief Creates an object array for a given item id
542          *
543          * @param integer $item_id
544          *
545          * @return object array
546          */
547         public static function createObjectFromItemID($item_id)
548         {
549                 $item = Item::selectFirst([], ['id' => $item_id, 'parent-network' => Protocol::NATIVE_SUPPORT]);
550
551                 if (!DBA::isResult($item)) {
552                         return false;
553                 }
554
555                 $data = ['@context' => ActivityPub::CONTEXT];
556                 $data = array_merge($data, self::createNote($item));
557
558                 return $data;
559         }
560
561         /**
562          * @brief Returns a tag array for a given item array
563          *
564          * @param array $item
565          *
566          * @return array of tags
567          */
568         private static function createTagList($item)
569         {
570                 $tags = [];
571
572                 $terms = Term::tagArrayFromItemId($item['id'], TERM_MENTION);
573                 foreach ($terms as $term) {
574                         $contact = Contact::getDetailsByURL($term['url']);
575                         if (!empty($contact['addr'])) {
576                                 $mention = '@' . $contact['addr'];
577                         } else {
578                                 $mention = '@' . $term['url'];
579                         }
580
581                         $tags[] = ['type' => 'Mention', 'href' => $term['url'], 'name' => $mention];
582                 }
583                 return $tags;
584         }
585
586         /**
587          * @brief Fetches the "context" value for a givem item array from the "conversation" table
588          *
589          * @param array $item
590          *
591          * @return string with context url
592          */
593         private static function fetchContextURLForItem($item)
594         {
595                 $conversation = DBA::selectFirst('conversation', ['conversation-href', 'conversation-uri'], ['item-uri' => $item['parent-uri']]);
596                 if (DBA::isResult($conversation) && !empty($conversation['conversation-href'])) {
597                         $context_uri = $conversation['conversation-href'];
598                 } elseif (DBA::isResult($conversation) && !empty($conversation['conversation-uri'])) {
599                         $context_uri = $conversation['conversation-uri'];
600                 } else {
601                         $context_uri = str_replace('/objects/', '/context/', $item['parent-uri']);
602                 }
603                 return $context_uri;
604         }
605
606         /**
607          * @brief Creates a note/article object array
608          *
609          * @param array $item
610          *
611          * @return object array
612          */
613         private static function createNote($item)
614         {
615                 if (!empty($item['title'])) {
616                         $type = 'Article';
617                 } else {
618                         $type = 'Note';
619                 }
620
621                 if ($item['deleted']) {
622                         $type = 'Tombstone';
623                 }
624
625                 $data = [];
626                 $data['id'] = $item['uri'];
627                 $data['type'] = $type;
628
629                 if ($item['deleted']) {
630                         return $data;
631                 }
632
633                 $data['summary'] = null; // Ignore by now
634
635                 if ($item['uri'] != $item['thr-parent']) {
636                         $data['inReplyTo'] = $item['thr-parent'];
637                 } else {
638                         $data['inReplyTo'] = null;
639                 }
640
641                 $data['diaspora:guid'] = $item['guid'];
642                 $data['published'] = DateTimeFormat::utc($item["created"]."+00:00", DateTimeFormat::ATOM);
643
644                 if ($item["created"] != $item["edited"]) {
645                         $data['updated'] = DateTimeFormat::utc($item["edited"]."+00:00", DateTimeFormat::ATOM);
646                 }
647
648                 $data['url'] = $item['plink'];
649                 $data['attributedTo'] = $item['author-link'];
650                 $data['actor'] = $item['author-link'];
651                 $data['sensitive'] = false; // - Query NSFW
652                 $data['context'] = self::fetchContextURLForItem($item);
653
654                 if (!empty($item['title'])) {
655                         $data['name'] = BBCode::convert($item['title'], false, 7);
656                 }
657
658                 $data['content'] = BBCode::convert($item['body'], false, 7);
659                 $data['source'] = ['content' => $item['body'], 'mediaType' => "text/bbcode"];
660
661                 if (!empty($item['signed_text']) && ($item['uri'] != $item['thr-parent'])) {
662                         $data['diaspora:comment'] = $item['signed_text'];
663                 }
664
665                 $data['attachment'] = []; // @ToDo
666                 $data['tag'] = self::createTagList($item);
667                 $data = array_merge($data, self::createPermissionBlockForItem($item));
668
669                 return $data;
670         }
671
672         /**
673          * @brief Transmits a profile deletion to a given inbox
674          *
675          * @param integer $uid User ID
676          * @param string $inbox Target inbox
677          */
678         public static function transmitProfileDeletion($uid, $inbox)
679         {
680                 $owner = User::getOwnerDataById($uid);
681                 $profile = APContact::getByURL($owner['url']);
682
683                 $data = ['@context' => 'https://www.w3.org/ns/activitystreams',
684                         'id' => System::baseUrl() . '/activity/' . System::createGUID(),
685                         'type' => 'Delete',
686                         'actor' => $owner['url'],
687                         'object' => self::profile($uid),
688                         'published' => DateTimeFormat::utcNow(DateTimeFormat::ATOM),
689                         'to' => [ActivityPub::PUBLIC_COLLECTION],
690                         'cc' => []];
691
692                 $signed = LDSignature::sign($data, $owner);
693
694                 logger('Deliver profile deletion for user ' . $uid . ' to ' . $inbox .' via ActivityPub', LOGGER_DEBUG);
695                 HTTPSignature::transmit($signed, $inbox, $uid);
696         }
697
698         /**
699          * @brief Transmits a profile change to a given inbox
700          *
701          * @param integer $uid User ID
702          * @param string $inbox Target inbox
703          */
704         public static function transmitProfileUpdate($uid, $inbox)
705         {
706                 $owner = User::getOwnerDataById($uid);
707                 $profile = APContact::getByURL($owner['url']);
708
709                 $data = ['@context' => 'https://www.w3.org/ns/activitystreams',
710                         'id' => System::baseUrl() . '/activity/' . System::createGUID(),
711                         'type' => 'Update',
712                         'actor' => $owner['url'],
713                         'object' => self::profile($uid),
714                         'published' => DateTimeFormat::utcNow(DateTimeFormat::ATOM),
715                         'to' => [$profile['followers']],
716                         'cc' => []];
717
718                 $signed = LDSignature::sign($data, $owner);
719
720                 logger('Deliver profile update for user ' . $uid . ' to ' . $inbox .' via ActivityPub', LOGGER_DEBUG);
721                 HTTPSignature::transmit($signed, $inbox, $uid);
722         }
723
724         /**
725          * @brief Transmits a given activity to a target
726          *
727          * @param array $activity
728          * @param string $target Target profile
729          * @param integer $uid User ID
730          */
731         public static function transmitActivity($activity, $target, $uid)
732         {
733                 $profile = APContact::getByURL($target);
734
735                 $owner = User::getOwnerDataById($uid);
736
737                 $data = ['@context' => 'https://www.w3.org/ns/activitystreams',
738                         'id' => System::baseUrl() . '/activity/' . System::createGUID(),
739                         'type' => $activity,
740                         'actor' => $owner['url'],
741                         'object' => $profile['url'],
742                         'to' => $profile['url']];
743
744                 logger('Sending activity ' . $activity . ' to ' . $target . ' for user ' . $uid, LOGGER_DEBUG);
745
746                 $signed = LDSignature::sign($data, $owner);
747                 HTTPSignature::transmit($signed, $profile['inbox'], $uid);
748         }
749
750         /**
751          * @brief Transmit a message that the contact request had been accepted
752          *
753          * @param string $target Target profile
754          * @param $id
755          * @param integer $uid User ID
756          */
757         public static function transmitContactAccept($target, $id, $uid)
758         {
759                 $profile = APContact::getByURL($target);
760
761                 $owner = User::getOwnerDataById($uid);
762                 $data = ['@context' => 'https://www.w3.org/ns/activitystreams',
763                         'id' => System::baseUrl() . '/activity/' . System::createGUID(),
764                         'type' => 'Accept',
765                         'actor' => $owner['url'],
766                         'object' => ['id' => $id, 'type' => 'Follow',
767                                 'actor' => $profile['url'],
768                                 'object' => $owner['url']],
769                         'to' => $profile['url']];
770
771                 logger('Sending accept to ' . $target . ' for user ' . $uid . ' with id ' . $id, LOGGER_DEBUG);
772
773                 $signed = LDSignature::sign($data, $owner);
774                 HTTPSignature::transmit($signed, $profile['inbox'], $uid);
775         }
776
777         /**
778          * @brief 
779          *
780          * @param string $target Target profile
781          * @param $id
782          * @param integer $uid User ID
783          */
784         public static function transmitContactReject($target, $id, $uid)
785         {
786                 $profile = APContact::getByURL($target);
787
788                 $owner = User::getOwnerDataById($uid);
789                 $data = ['@context' => 'https://www.w3.org/ns/activitystreams',
790                         'id' => System::baseUrl() . '/activity/' . System::createGUID(),
791                         'type' => 'Reject',
792                         'actor' => $owner['url'],
793                         'object' => ['id' => $id, 'type' => 'Follow',
794                                 'actor' => $profile['url'],
795                                 'object' => $owner['url']],
796                         'to' => $profile['url']];
797
798                 logger('Sending reject to ' . $target . ' for user ' . $uid . ' with id ' . $id, LOGGER_DEBUG);
799
800                 $signed = LDSignature::sign($data, $owner);
801                 HTTPSignature::transmit($signed, $profile['inbox'], $uid);
802         }
803
804         /**
805          * @brief 
806          *
807          * @param string $target Target profile
808          * @param integer $uid User ID
809          */
810         public static function transmitContactUndo($target, $uid)
811         {
812                 $profile = APContact::getByURL($target);
813
814                 $id = System::baseUrl() . '/activity/' . System::createGUID();
815
816                 $owner = User::getOwnerDataById($uid);
817                 $data = ['@context' => 'https://www.w3.org/ns/activitystreams',
818                         'id' => $id,
819                         'type' => 'Undo',
820                         'actor' => $owner['url'],
821                         'object' => ['id' => $id, 'type' => 'Follow',
822                                 'actor' => $owner['url'],
823                                 'object' => $profile['url']],
824                         'to' => $profile['url']];
825
826                 logger('Sending undo to ' . $target . ' for user ' . $uid . ' with id ' . $id, LOGGER_DEBUG);
827
828                 $signed = LDSignature::sign($data, $owner);
829                 HTTPSignature::transmit($signed, $profile['inbox'], $uid);
830         }
831 }