]> git.mxchange.org Git - friendica.git/blob - src/Protocol/ActivityPub/Queue.php
Added trust / isActivityGone
[friendica.git] / src / Protocol / ActivityPub / Queue.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2022, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Protocol\ActivityPub;
23
24 use Friendica\Core\Logger;
25 use Friendica\Database\Database;
26 use Friendica\Database\DBA;
27 use Friendica\DI;
28 use Friendica\Util\DateTimeFormat;
29
30 /**
31  * This class handles the processing of incoming posts
32  */
33 class Queue
34 {
35         /**
36          * Add activity to the queue
37          *
38          * @param array $activity
39          * @param string $type
40          * @param integer $uid
41          * @param string $http_signer
42          * @param boolean $push
43          * @return array
44          */
45         public static function add(array $activity, string $type, int $uid, string $http_signer, bool $push, bool $trust_source): array
46         {
47                 $fields = [
48                         'activity-id' => $activity['id'],
49                         'object-id'   => $activity['object_id'],
50                         'type'        => $type,
51                         'object-type' => $activity['object_type'],
52                         'activity'    => json_encode($activity, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
53                         'received'    => DateTimeFormat::utcNow(),
54                         'push'        => $push,
55                         'trust'       => $trust_source,
56                 ];
57
58                 if (!empty($activity['reply-to-id'])) {
59                         $fields['in-reply-to-id'] = $activity['reply-to-id'];
60                 }
61
62                 if (!empty($activity['context'])) {
63                         $fields['conversation'] = $activity['context'];
64                 } elseif (!empty($activity['conversation'])) {
65                         $fields['conversation'] = $activity['conversation'];
66                 }
67
68                 if (!empty($activity['object_object_type'])) {
69                         $fields['object-object-type'] = $activity['object_object_type'];
70                 }
71
72                 if (!empty($http_signer)) {
73                         $fields['signer'] = $http_signer;
74                 }
75
76                 DBA::insert('inbox-entry', $fields, Database::INSERT_IGNORE);
77
78                 $queue = DBA::selectFirst('inbox-entry', ['id'], ['activity-id' => $activity['id']]);
79                 if (!empty($queue['id'])) {
80                         $activity['entry-id'] = $queue['id'];
81                         DBA::insert('inbox-entry-receiver', ['queue-id' => $queue['id'], 'uid' => $uid], Database::INSERT_IGNORE);
82                 }
83                 return $activity;
84         }
85
86         /**
87          * Remove activity from the queue
88          *
89          * @param array $activity
90          * @return void
91          */
92         public static function remove(array $activity = [])
93         {
94                 if (empty($activity['entry-id'])) {
95                         return;
96                 }
97                 DBA::delete('inbox-entry', ['id' => $activity['entry-id']]);
98         }
99
100         /**
101          * Delete all entries that depend on the given worker id
102          *
103          * @param integer $wid
104          * @return void
105          */
106         public static function deleteByWorkerId(int $wid)
107         {
108                 $entries = DBA::select('inbox-entry', ['id'], ['wid' => $wid]);
109                 while ($entry = DBA::fetch($entries)) {
110                         self::deleteById($entry['id']);
111                 }
112                 DBA::close($entries);
113         }
114
115         /**
116          * Delete recursively an entry and all their children
117          *
118          * @param integer $id
119          * @return void
120          */
121         public static function deleteById(int $id)
122         {
123                 $entry = DBA::selectFirst('inbox-entry', ['id', 'object-id'], ['id' => $id]);
124                 if (empty($entry)) {
125                         return;
126                 }
127
128                 $children = DBA::select('inbox-entry', ['id'], ['in-reply-to-id' => $entry['object-id']]);
129                 while ($child = DBA::fetch($children)) {
130                         self::deleteById($child['id']);
131                 }
132                 DBA::close($children);
133                 DBA::delete('inbox-entry', ['id' => $entry['id']]);
134         }
135
136         /**
137          * Set the worker id for the queue entry
138          *
139          * @param array $activity
140          * @param int   $wid
141          * @return void
142          */
143         public static function setWorkerId(array $activity, int $wid)
144         {
145                 if (empty($activity['entry-id']) || empty($wid)) {
146                         return;
147                 }
148                 DBA::update('inbox-entry', ['wid' => $wid], ['id' => $activity['entry-id']]);
149         }
150
151         /**
152          * Check if there is an assigned worker task
153          *
154          * @param array $activity
155          * @return bool
156          */
157         public static function hasWorker(array $activity = []): bool
158         {
159                 if (empty($activity['worker-id'])) {
160                         return false;
161                 }
162                 return DBA::exists('workerqueue', ['id' => $activity['worker-id'], 'done' => false]);
163         }
164
165         /**
166          * Process the activity with the given id
167          *
168          * @param integer $id
169          * @return void
170          */
171         public static function process(int $id)
172         {
173                 $entry = DBA::selectFirst('inbox-entry', [], ['id' => $id]);
174                 if (empty($entry)) {
175                         return;
176                 }
177
178                 Logger::debug('Processing queue entry', ['id' => $entry['id'], 'type' => $entry['type'], 'object-type' => $entry['object-type'], 'uri' => $entry['object-id'], 'in-reply-to' => $entry['in-reply-to-id']]);
179
180                 $activity = json_decode($entry['activity'], true);
181                 $type     = $entry['type'];
182                 $push     = $entry['push'];
183
184                 $activity['entry-id']        = $entry['id'];
185                 $activity['worker-id']       = $entry['wid'];
186                 $activity['recursion-depth'] = 0;
187
188                 $receivers = DBA::select('inbox-entry-receiver', ['uid'], ['queue-id' => $entry['id']]);
189                 while ($receiver = DBA::fetch($receivers)) {
190                         if (!in_array($receiver['uid'], $activity['receiver'])) {
191                                 $activity['receiver'][] = $receiver['uid'];
192                         }
193                 }
194                 DBA::close($receivers);
195
196                 if (!Receiver::routeActivities($activity, $type, $push)) {
197                         self::remove($activity);
198                 }
199         }
200
201         /**
202          * Process all activities
203          *
204          * @return void
205          */
206         public static function processAll()
207         {
208                 $entries = DBA::select('inbox-entry', ['id', 'type', 'object-type', 'object-id', 'in-reply-to-id'], ["`trust` AND `wid` IS NULL"], ['order' => ['id' => true]]);
209                 while ($entry = DBA::fetch($entries)) {
210                         // We don't need to process entries that depend on already existing entries.
211                         if (!empty($entry['in-reply-to-id']) && DBA::exists('inbox-entry', ["`id` != ? AND `object-id` = ?", $entry['id'], $entry['in-reply-to-id']])) {
212                                 continue;
213                         }
214                         Logger::debug('Process leftover entry', $entry);
215                         self::process($entry['id']);
216                 }
217         }
218
219         /**
220          * Clear old activities
221          *
222          * @return void
223          */
224         public static function clear()
225         {
226                 // We delete all entries that aren't associated with a worker entry after seven days.
227                 // The other entries are deleted when the worker deferred for too long.
228                 DBA::delete('inbox-entry', ["`wid` IS NULL AND `received` < ?", DateTimeFormat::utc('now - 7 days')]);
229
230                 // Optimizing this table only last seconds
231                 if (DI::config()->get('system', 'optimize_tables')) {
232                         Logger::info('Optimize start');
233                         DBA::e("OPTIMIZE TABLE `inbox-entry`");
234                         Logger::info('Optimize end');
235                 }
236         }
237
238         /**
239          * Process all activities that are children of a given post url
240          *
241          * @param string $uri
242          * @return void
243          */
244         public static function processReplyByUri(string $uri)
245         {
246                 $entries = DBA::select('inbox-entry', ['id'], ["`in-reply-to-id` = ? AND `object-id` != ?", $uri, $uri]);
247                 while ($entry = DBA::fetch($entries)) {
248                         self::process($entry['id']);
249                 }
250         }
251 }