]> git.mxchange.org Git - friendica.git/blob - src/Database/PostUpdate.php
License updated
[friendica.git] / src / Database / PostUpdate.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2023, 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\Database;
23
24 use Friendica\Core\Logger;
25 use Friendica\Core\Protocol;
26 use Friendica\DI;
27 use Friendica\Model\Contact;
28 use Friendica\Model\Conversation;
29 use Friendica\Model\GServer;
30 use Friendica\Model\Item;
31 use Friendica\Model\ItemURI;
32 use Friendica\Model\Photo;
33 use Friendica\Model\Post;
34 use Friendica\Model\Post\Category;
35 use Friendica\Model\Tag;
36 use Friendica\Model\Verb;
37 use Friendica\Protocol\ActivityPub\Processor;
38 use Friendica\Protocol\ActivityPub\Receiver;
39 use Friendica\Util\JsonLD;
40 use Friendica\Util\Strings;
41
42 /**
43  * These database-intensive post update routines are meant to be executed in the background by the cronjob.
44  *
45  * If there is a need for a intensive migration after a database structure change, update this file
46  * by adding a new method at the end with the number of the new DB_UPDATE_VERSION.
47  */
48 class PostUpdate
49 {
50         // Needed for the helper function to read from the legacy term table
51         const OBJECT_TYPE_POST  = 1;
52
53         const VERSION = 1507;
54
55         /**
56          * Calls the post update functions
57          */
58         public static function update()
59         {
60                 if (!self::update1297()) {
61                         return false;
62                 }
63                 if (!self::update1322()) {
64                         return false;
65                 }
66                 if (!self::update1329()) {
67                         return false;
68                 }
69                 if (!self::update1341()) {
70                         return false;
71                 }
72                 if (!self::update1342()) {
73                         return false;
74                 }
75                 if (!self::update1345()) {
76                         return false;
77                 }
78                 if (!self::update1346()) {
79                         return false;
80                 }
81                 if (!self::update1347()) {
82                         return false;
83                 }
84                 if (!self::update1348()) {
85                         return false;
86                 }
87                 if (!self::update1349()) {
88                         return false;
89                 }
90                 if (!self::update1383()) {
91                         return false;
92                 }
93                 if (!self::update1384()) {
94                         return false;
95                 }
96                 if (!self::update1400()) {
97                         return false;
98                 }
99                 if (!self::update1424()) {
100                         return false;
101                 }
102                 if (!self::update1425()) {
103                         return false;
104                 }
105                 if (!self::update1426()) {
106                         return false;
107                 }
108                 if (!self::update1427()) {
109                         return false;
110                 }
111                 if (!self::update1452()) {
112                         return false;
113                 }
114                 if (!self::update1483()) {
115                         return false;
116                 }
117                 if (!self::update1484()) {
118                         return false;
119                 }
120                 if (!self::update1507()) {
121                         return false;
122                 }
123                 return true;
124         }
125
126         /**
127          * Set the delivery queue count to a negative value for all items preceding the feature.
128          *
129          * @return bool "true" when the job is done
130          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
131          */
132         private static function update1297()
133         {
134                 // Was the script completed?
135                 if (DI::keyValue()->get('post_update_version') >= 1297) {
136                         return true;
137                 }
138
139                 if (!DBStructure::existsTable('item-delivery-data')) {
140                         DI::keyValue()->set('post_update_version', 1297);
141                         return true;
142                 }
143
144                 $max_item_delivery_data = DBA::selectFirst('item-delivery-data', ['iid'], ['queue_count > 0 OR queue_done > 0'], ['order' => ['iid']]);
145                 $max_iid = $max_item_delivery_data['iid'] ?? 0;
146
147                 Logger::info('Start update1297 with max iid: ' . $max_iid);
148
149                 $condition = ['`queue_count` = 0 AND `iid` < ?', $max_iid];
150
151                 DBA::update('item-delivery-data', ['queue_count' => -1], $condition);
152
153                 if (DBA::errorNo() != 0) {
154                         Logger::error('Database error ' . DBA::errorNo() . ':' . DBA::errorMessage());
155                         return false;
156                 }
157
158                 Logger::info('Processed rows: ' . DBA::affectedRows());
159
160                 DI::keyValue()->set('post_update_version', 1297);
161
162                 Logger::info('Done');
163
164                 return true;
165         }
166         /**
167          * Remove contact duplicates
168          *
169          * @return bool "true" when the job is done
170          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
171          */
172         private static function update1322()
173         {
174                 // Was the script completed?
175                 if (DI::keyValue()->get('post_update_version') >= 1322) {
176                         return true;
177                 }
178
179                 Logger::info('Start');
180
181                 $contacts = DBA::p("SELECT `nurl`, `uid` FROM `contact`
182                         WHERE EXISTS (SELECT `nurl` FROM `contact` AS `c2`
183                                 WHERE `c2`.`nurl` = `contact`.`nurl` AND `c2`.`id` != `contact`.`id` AND `c2`.`uid` = `contact`.`uid` AND `c2`.`network` IN (?, ?, ?) AND NOT `deleted`)
184                         AND (`network` IN (?, ?, ?) OR (`uid` = ?)) AND NOT `deleted` GROUP BY `nurl`, `uid`",
185                         Protocol::DIASPORA, Protocol::OSTATUS, Protocol::ACTIVITYPUB,
186                         Protocol::DIASPORA, Protocol::OSTATUS, Protocol::ACTIVITYPUB, 0);
187
188                 while ($contact = DBA::fetch($contacts)) {
189                         Logger::info('Remove duplicates', ['nurl' => $contact['nurl'], 'uid' => $contact['uid']]);
190                         Contact::removeDuplicates($contact['nurl'], $contact['uid']);
191                 }
192
193                 DBA::close($contact);
194                 DI::keyValue()->set('post_update_version', 1322);
195
196                 Logger::info('Done');
197
198                 return true;
199         }
200
201         /**
202          * update user notification data
203          *
204          * @return bool "true" when the job is done
205          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
206          */
207         private static function update1329()
208         {
209                 // Was the script completed?
210                 if (DI::keyValue()->get('post_update_version') >= 1329) {
211                         return true;
212                 }
213
214                 if (!DBStructure::existsTable('item')) {
215                         DI::keyValue()->set('post_update_version', 1329);
216                         return true;
217                 }
218
219                 $id = DI::keyValue()->get('post_update_version_1329_id') ?? 0;
220
221                 Logger::info('Start', ['item' => $id]);
222
223                 $start_id = $id;
224                 $rows = 0;
225                 $condition = ["`id` > ?", $id];
226                 $params = ['order' => ['id'], 'limit' => 10000];
227                 $items = DBA::select('item', ['id', 'uri-id', 'uid'], $condition, $params);
228
229                 if (DBA::errorNo() != 0) {
230                         Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
231                         return false;
232                 }
233
234                 while ($item = DBA::fetch($items)) {
235                         $id = $item['id'];
236
237                         Post\UserNotification::setNotification($item['uri-id'], $item['uid']);
238
239                         ++$rows;
240                 }
241                 DBA::close($items);
242
243                 DI::keyValue()->set('post_update_version_1329_id', $id);
244
245                 Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
246
247                 if ($start_id == $id) {
248                         DI::keyValue()->set('post_update_version', 1329);
249                         Logger::info('Done');
250                         return true;
251                 }
252
253                 return false;
254         }
255
256         /**
257          * Fill the "tag" table with tags and mentions from the body
258          *
259          * @return bool "true" when the job is done
260          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
261          */
262         private static function update1341()
263         {
264                 // Was the script completed?
265                 if (DI::keyValue()->get('post_update_version') >= 1341) {
266                         return true;
267                 }
268
269                 if (!DBStructure::existsTable('item-content')) {
270                         DI::keyValue()->set('post_update_version', 1342);
271                         return true;
272                 }
273
274                 $id = DI::keyValue()->get('post_update_version_1341_id') ?? 0;
275
276                 Logger::info('Start', ['item' => $id]);
277
278                 $rows = 0;
279
280                 $items = DBA::p("SELECT `uri-id`,`body` FROM `item-content` WHERE
281                         (`body` LIKE ? OR `body` LIKE ? OR `body` LIKE ?) AND `uri-id` >= ?
282                         ORDER BY `uri-id` LIMIT 100000", '%#%', '%@%', '%!%', $id);
283
284                 if (DBA::errorNo() != 0) {
285                         Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
286                         return false;
287                 }
288
289                 while ($item = DBA::fetch($items)) {
290                         Tag::storeFromBody($item['uri-id'], $item['body'], '#!@', false);
291                         $id = $item['uri-id'];
292                         ++$rows;
293                         if ($rows % 1000 == 0) {
294                                 DI::keyValue()->set('post_update_version_1341_id', $id);
295                         }
296                 }
297                 DBA::close($items);
298
299                 DI::keyValue()->set('post_update_version_1341_id', $id);
300
301                 Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
302
303                 // When there are less than 1,000 items processed this means that we reached the end
304                 // The other entries will then be processed with the regular functionality
305                 if ($rows < 1000) {
306                         DI::keyValue()->set('post_update_version', 1341);
307                         Logger::info('Done');
308                         return true;
309                 }
310
311                 return false;
312         }
313
314         /**
315          * Fill the "tag" table with tags and mentions from the "term" table
316          *
317          * @return bool "true" when the job is done
318          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
319          */
320         private static function update1342()
321         {
322                 // Was the script completed?
323                 if (DI::keyValue()->get('post_update_version') >= 1342) {
324                         return true;
325                 }
326
327                 if (!DBStructure::existsTable('term') || !DBStructure::existsTable('item-content')) {
328                         DI::keyValue()->set('post_update_version', 1342);
329                         return true;
330                 }
331
332                 $id = DI::keyValue()->get('post_update_version_1342_id') ?? 0;
333
334                 Logger::info('Start', ['item' => $id]);
335
336                 $rows = 0;
337
338                 $terms = DBA::p("SELECT `term`.`tid`, `item`.`uri-id`, `term`.`type`, `term`.`term`, `term`.`url`, `item-content`.`body`
339                         FROM `term`
340                         INNER JOIN `item` ON `item`.`id` = `term`.`oid`
341                         INNER JOIN `item-content` ON `item-content`.`uri-id` = `item`.`uri-id`
342                         WHERE term.type IN (?, ?, ?, ?) AND `tid` >= ? ORDER BY `tid` LIMIT 100000",
343                         Tag::HASHTAG, Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION, $id);
344
345                 if (DBA::errorNo() != 0) {
346                         Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
347                         return false;
348                 }
349
350                 while ($term = DBA::fetch($terms)) {
351                         if (($term['type'] == Tag::MENTION) && !empty($term['url']) && !strstr($term['body'], $term['url'])) {
352                 $condition = ['nurl' => Strings::normaliseLink($term['url']), 'uid' => 0, 'deleted' => false];
353                 $contact = DBA::selectFirst('contact', ['url', 'alias'], $condition, ['order' => ['id']]);
354                 if (!DBA::isResult($contact)) {
355                         $ssl_url = str_replace('http://', 'https://', $term['url']);
356                         $condition = ['`alias` IN (?, ?, ?) AND `uid` = ? AND NOT `deleted`', $term['url'], Strings::normaliseLink($term['url']), $ssl_url, 0];
357                         $contact = DBA::selectFirst('contact', ['url', 'alias'], $condition, ['order' => ['id']]);
358                 }
359
360                 if (DBA::isResult($contact) && (!strstr($term['body'], $contact['url']) && (empty($contact['alias']) || !strstr($term['body'], $contact['alias'])))) {
361                         $term['type'] = Tag::IMPLICIT_MENTION;
362                 }
363                         }
364
365                         Tag::store($term['uri-id'], $term['type'], $term['term'], $term['url']);
366
367                         $id = $term['tid'];
368                         ++$rows;
369                         if ($rows % 1000 == 0) {
370                                 DI::keyValue()->set('post_update_version_1342_id', $id);
371                         }
372                 }
373                 DBA::close($terms);
374
375                 DI::keyValue()->set('post_update_version_1342_id', $id);
376
377                 Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
378
379                 // When there are less than 1,000 items processed this means that we reached the end
380                 // The other entries will then be processed with the regular functionality
381                 if ($rows < 1000) {
382                         DI::keyValue()->set('post_update_version', 1342);
383                         Logger::info('Done');
384                         return true;
385                 }
386
387                 return false;
388         }
389
390         /**
391          * Fill the "post-delivery-data" table with data from the "item-delivery-data" table
392          *
393          * @return bool "true" when the job is done
394          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
395          */
396         private static function update1345()
397         {
398                 // Was the script completed?
399                 if (DI::keyValue()->get('post_update_version') >= 1345) {
400                         return true;
401                 }
402
403                 if (!DBStructure::existsTable('item-delivery-data')) {
404                         DI::keyValue()->set('post_update_version', 1345);
405                         return true;
406                 }
407
408                 $id = DI::keyValue()->get('post_update_version_1345_id') ?? 0;
409
410                 Logger::info('Start', ['item' => $id]);
411
412                 $rows = 0;
413
414                 $deliveries = DBA::p("SELECT `uri-id`, `iid`, `item-delivery-data`.`postopts`, `item-delivery-data`.`inform`,
415                         `queue_count`, `queue_done`, `activitypub`, `dfrn`, `diaspora`, `ostatus`, `legacy_dfrn`, `queue_failed`
416                         FROM `item-delivery-data`
417                         INNER JOIN `item` ON `item`.`id` = `item-delivery-data`.`iid`
418                         WHERE `iid` >= ? ORDER BY `iid` LIMIT 10000", $id);
419
420                 if (DBA::errorNo() != 0) {
421                         Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
422                         return false;
423                 }
424
425                 while ($delivery = DBA::fetch($deliveries)) {
426                         $id = $delivery['iid'];
427                         unset($delivery['iid']);
428                         DBA::insert('post-delivery-data', $delivery, Database::INSERT_UPDATE);
429                         ++$rows;
430                 }
431                 DBA::close($deliveries);
432
433                 DI::keyValue()->set('post_update_version_1345_id', $id);
434
435                 Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
436
437                 // When there are less than 100 items processed this means that we reached the end
438                 // The other entries will then be processed with the regular functionality
439                 if ($rows < 100) {
440                         DI::keyValue()->set('post_update_version', 1345);
441                         Logger::info('Done');
442                         return true;
443                 }
444
445                 return false;
446         }
447
448         /**
449          * Generates the legacy item.file field string from an item ID.
450          * Includes only file and category terms.
451          *
452          * @param int $item_id
453          * @return string
454          * @throws \Exception
455          */
456         private static function fileTextFromItemId($item_id)
457         {
458                 $file_text = '';
459
460                 $condition = ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => [Category::FILE, Category::CATEGORY]];
461                 $tags = DBA::selectToArray('term', ['type', 'term', 'url'], $condition);
462                 foreach ($tags as $tag) {
463                         if ($tag['type'] == Category::CATEGORY) {
464                                 $file_text .= '<' . $tag['term'] . '>';
465                         } else {
466                                 $file_text .= '[' . $tag['term'] . ']';
467                         }
468                 }
469
470                 return $file_text;
471         }
472
473         /**
474          * Fill the "tag" table with tags and mentions from the "term" table
475          *
476          * @return bool "true" when the job is done
477          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
478          */
479         private static function update1346()
480         {
481                 // Was the script completed?
482                 if (DI::keyValue()->get('post_update_version') >= 1346) {
483                         return true;
484                 }
485
486                 if (!DBStructure::existsTable('term')) {
487                         DI::keyValue()->set('post_update_version', 1346);
488                         return true;
489                 }
490
491                 $id = DI::keyValue()->get('post_update_version_1346_id') ?? 0;
492
493                 Logger::info('Start', ['item' => $id]);
494
495                 $rows = 0;
496
497                 $terms = DBA::select('term', ['oid'],
498                         ["`type` IN (?, ?) AND `oid` >= ?", Category::CATEGORY, Category::FILE, $id],
499                         ['order' => ['oid'], 'limit' => 1000, 'group_by' => ['oid']]);
500
501                 if (DBA::errorNo() != 0) {
502                         Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
503                         return false;
504                 }
505
506                 while ($term = DBA::fetch($terms)) {
507                         $item = Post::selectFirst(['uri-id', 'uid'], ['id' => $term['oid']]);
508                         if (!DBA::isResult($item)) {
509                                 continue;
510                         }
511
512                         $file = self::fileTextFromItemId($term['oid']);
513                         if (!empty($file)) {
514                                 Category::storeTextByURIId($item['uri-id'], $item['uid'], $file);
515                         }
516
517                         $id = $term['oid'];
518                         ++$rows;
519                         if ($rows % 100 == 0) {
520                                 DI::keyValue()->set('post_update_version_1346_id', $id);
521                         }
522                 }
523                 DBA::close($terms);
524
525                 DI::keyValue()->set('post_update_version_1346_id', $id);
526
527                 Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
528
529                 // When there are less than 10 items processed this means that we reached the end
530                 // The other entries will then be processed with the regular functionality
531                 if ($rows < 10) {
532                         DI::keyValue()->set('post_update_version', 1346);
533                         Logger::info('Done');
534                         return true;
535                 }
536
537                 return false;
538         }
539
540         /**
541          * update the "vid" (verb) field in the item table
542          *
543          * @return bool "true" when the job is done
544          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
545          * @throws \ImagickException
546          */
547         private static function update1347()
548         {
549                 // Was the script completed?
550                 if (DI::keyValue()->get('post_update_version') >= 1347) {
551                         return true;
552                 }
553
554                 if (!DBStructure::existsTable('item-activity') || !DBStructure::existsTable('item')) {
555                         DI::keyValue()->set('post_update_version', 1347);
556                         return true;
557                 }
558
559                 $id = DI::keyValue()->get('post_update_version_1347_id') ?? 0;
560
561                 Logger::info('Start', ['item' => $id]);
562
563                 $start_id = $id;
564                 $rows = 0;
565
566                 $items = DBA::p("SELECT `item`.`id`, `item`.`verb` AS `item-verb`, `item-content`.`verb`, `item-activity`.`activity`
567                         FROM `item` LEFT JOIN `item-content` ON `item-content`.`uri-id` = `item`.`uri-id`
568                         LEFT JOIN `item-activity` ON `item-activity`.`uri-id` = `item`.`uri-id` AND `item`.`gravity` = ?
569                         WHERE `item`.`id` >= ? AND `item`.`vid` IS NULL ORDER BY `item`.`id` LIMIT 10000", Item::GRAVITY_ACTIVITY, $id);
570
571                 if (DBA::errorNo() != 0) {
572                         Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
573                         return false;
574                 }
575
576                 while ($item = DBA::fetch($items)) {
577                         $id = $item['id'];
578                         $verb = $item['item-verb'];
579                         if (empty($verb)) {
580                                 $verb = $item['verb'];
581                         }
582                         if (empty($verb) && is_int($item['activity'])) {
583                                 $verb = Item::ACTIVITIES[$item['activity']];
584                         }
585                         if (empty($verb)) {
586                                 continue;
587                         }
588
589                         DBA::update('item', ['vid' => Verb::getID($verb)], ['id' => $item['id']]);
590                         ++$rows;
591                 }
592                 DBA::close($items);
593
594                 DI::keyValue()->set('post_update_version_1347_id', $id);
595
596                 Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
597
598                 if ($start_id == $id) {
599                         DI::keyValue()->set('post_update_version', 1347);
600                         Logger::info('Done');
601                         return true;
602                 }
603
604                 return false;
605         }
606
607         /**
608          * update the "gsid" (global server id) field in the contact table
609          *
610          * @return bool "true" when the job is done
611          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
612          * @throws \ImagickException
613          */
614         private static function update1348()
615         {
616                 // Was the script completed?
617                 if (DI::keyValue()->get('post_update_version') >= 1348) {
618                         return true;
619                 }
620
621                 $id = DI::keyValue()->get('post_update_version_1348_id') ?? 0;
622
623                 Logger::info('Start', ['contact' => $id]);
624
625                 $start_id = $id;
626                 $rows = 0;
627                 $condition = ["`id` > ? AND `gsid` IS NULL AND `baseurl` != '' AND NOT `baseurl` IS NULL", $id];
628                 $params = ['order' => ['id'], 'limit' => 10000];
629                 $contacts = DBA::select('contact', ['id', 'baseurl'], $condition, $params);
630
631                 if (DBA::errorNo() != 0) {
632                         Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
633                         return false;
634                 }
635
636                 while ($contact = DBA::fetch($contacts)) {
637                         $id = $contact['id'];
638
639                         DBA::update('contact',
640                                 ['gsid' => GServer::getID($contact['baseurl'], true), 'baseurl' => GServer::cleanURL($contact['baseurl'])],
641                                 ['id' => $contact['id']]);
642
643                         ++$rows;
644                 }
645                 DBA::close($contacts);
646
647                 DI::keyValue()->set('post_update_version_1348_id', $id);
648
649                 Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
650
651                 if ($start_id == $id) {
652                         DI::keyValue()->set('post_update_version', 1348);
653                         Logger::info('Done');
654                         return true;
655                 }
656
657                 return false;
658         }
659
660         /**
661          * update the "gsid" (global server id) field in the apcontact table
662          *
663          * @return bool "true" when the job is done
664          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
665          * @throws \ImagickException
666          */
667         private static function update1349()
668         {
669                 // Was the script completed?
670                 if (DI::keyValue()->get('post_update_version') >= 1349) {
671                         return true;
672                 }
673
674                 $id = DI::keyValue()->get('post_update_version_1349_id') ?? '';
675
676                 Logger::info('Start', ['apcontact' => $id]);
677
678                 $start_id = $id;
679                 $rows = 0;
680                 $condition = ["`url` > ? AND `gsid` IS NULL AND `baseurl` != '' AND NOT `baseurl` IS NULL", $id];
681                 $params = ['order' => ['url'], 'limit' => 10000];
682                 $apcontacts = DBA::select('apcontact', ['url', 'baseurl'], $condition, $params);
683
684                 if (DBA::errorNo() != 0) {
685                         Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
686                         return false;
687                 }
688
689                 while ($apcontact = DBA::fetch($apcontacts)) {
690                         $id = $apcontact['url'];
691
692                         DBA::update('apcontact',
693                                 ['gsid' => GServer::getID($apcontact['baseurl'], true), 'baseurl' => GServer::cleanURL($apcontact['baseurl'])],
694                                 ['url' => $apcontact['url']]);
695
696                         ++$rows;
697                 }
698                 DBA::close($apcontacts);
699
700                 DI::keyValue()->set('post_update_version_1349_id', $id);
701
702                 Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
703
704                 if ($start_id == $id) {
705                         DI::keyValue()->set('post_update_version', 1349);
706                         Logger::info('Done');
707                         return true;
708                 }
709
710                 return false;
711         }
712
713         /**
714          * Remove orphaned photo entries
715          *
716          * @return bool "true" when the job is done
717          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
718          * @throws \ImagickException
719          */
720         private static function update1383()
721         {
722                 // Was the script completed?
723                 if (DI::keyValue()->get('post_update_version') >= 1383) {
724                         return true;
725                 }
726
727                 Logger::info('Start');
728
729                 $deleted = 0;
730                 $avatar = [4 => 'photo', 5 => 'thumb', 6 => 'micro'];
731
732                 $photos = DBA::select('photo', ['id', 'contact-id', 'resource-id', 'scale'], ["`contact-id` != ? AND `album` = ?", 0, Photo::CONTACT_PHOTOS]);
733                 while ($photo = DBA::fetch($photos)) {
734                         $delete = !in_array($photo['scale'], [4, 5, 6]);
735
736                         if (!$delete) {
737                                 // Check if there is a contact entry with that photo
738                                 $delete = !DBA::exists('contact', ["`id` = ? AND `" . $avatar[$photo['scale']] . "` LIKE ?",
739                                         $photo['contact-id'], '%' . $photo['resource-id'] . '%']);
740                         }
741
742                         if ($delete) {
743                                 Photo::delete(['id' => $photo['id']]);
744                                 $deleted++;
745                         }
746                 }
747                 DBA::close($photos);
748
749                 DI::keyValue()->set('post_update_version', 1383);
750                 Logger::info('Done', ['deleted' => $deleted]);
751                 return true;
752         }
753
754         /**
755          * update the "hash" field in the photo table
756          *
757          * @return bool "true" when the job is done
758          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
759          * @throws \ImagickException
760          */
761         private static function update1384()
762         {
763                 // Was the script completed?
764                 if (DI::keyValue()->get('post_update_version') >= 1384) {
765                         return true;
766                 }
767
768                 $condition = ["`hash` IS NULL"];
769                 Logger::info('Start', ['rest' => DBA::count('photo', $condition)]);
770
771                 $rows = 0;
772                 $photos = DBA::select('photo', [], $condition, ['limit' => 100]);
773
774                 if (DBA::errorNo() != 0) {
775                         Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
776                         return false;
777                 }
778
779                 while ($photo = DBA::fetch($photos)) {
780                         $img = Photo::getImageForPhoto($photo);
781                         if (!empty($img)) {
782                                 $md5 = md5($img->asString());
783                         } else {
784                                 $md5 = '';
785                         }
786                         DBA::update('photo', ['hash' => $md5], ['id' => $photo['id']]);
787                         ++$rows;
788                 }
789                 DBA::close($photos);
790
791                 Logger::info('Processed', ['rows' => $rows]);
792
793                 if ($rows <= 100) {
794                         DI::keyValue()->set('post_update_version', 1384);
795                         Logger::info('Done');
796                         return true;
797                 }
798
799                 return false;
800         }
801
802         /**
803          * update the "external-id" field in the post table
804          *
805          * @return bool "true" when the job is done
806          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
807          * @throws \ImagickException
808          */
809         private static function update1400()
810         {
811                 // Was the script completed?
812                 if (DI::keyValue()->get('post_update_version') >= 1400) {
813                         return true;
814                 }
815
816                 if (!DBStructure::existsTable('item')) {
817                         DI::keyValue()->set('post_update_version', 1400);
818                         return true;
819                 }
820
821                 $condition = ["`extid` != ? AND EXISTS(SELECT `id` FROM `post-user` WHERE `uri-id` = `item`.`uri-id` AND `uid` = `item`.`uid` AND `external-id` IS NULL)", ''];
822                 Logger::info('Start', ['rest' => DBA::count('item', $condition)]);
823
824                 $rows = 0;
825                 $items = DBA::select('item', ['uri-id', 'uid', 'extid'], $condition, ['order' => ['id'], 'limit' => 10000]);
826
827                 if (DBA::errorNo() != 0) {
828                         Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
829                         return false;
830                 }
831
832                 while ($item = DBA::fetch($items)) {
833                         Post::update(['external-id' => ItemURI::getIdByURI($item['extid'])], ['uri-id' => $item['uri-id'], 'uid' => $item['uid']]);
834                         ++$rows;
835                 }
836                 DBA::close($items);
837
838                 Logger::info('Processed', ['rows' => $rows]);
839
840                 if ($rows <= 100) {
841                         DI::keyValue()->set('post_update_version', 1400);
842                         Logger::info('Done');
843                         return true;
844                 }
845
846                 return false;
847         }
848
849         /**
850          * update the "uri-id" field in the contact table
851          *
852          * @return bool "true" when the job is done
853          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
854          * @throws \ImagickException
855          */
856         private static function update1424()
857         {
858                 // Was the script completed?
859                 if (DI::keyValue()->get('post_update_version') >= 1424) {
860                         return true;
861                 }
862
863                 $condition = ["`uri-id` IS NULL"];
864                 Logger::info('Start', ['rest' => DBA::count('contact', $condition)]);
865
866                 $rows = 0;
867                 $contacts = DBA::select('contact', ['id', 'url'], $condition, ['limit' => 1000]);
868
869                 if (DBA::errorNo() != 0) {
870                         Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
871                         return false;
872                 }
873
874                 while ($contact = DBA::fetch($contacts)) {
875                         DBA::update('contact', ['uri-id' => ItemURI::getIdByURI($contact['url'])], ['id' => $contact['id']]);
876                         ++$rows;
877                 }
878                 DBA::close($contacts);
879
880                 Logger::info('Processed', ['rows' => $rows]);
881
882                 if ($rows <= 100) {
883                         DI::keyValue()->set('post_update_version', 1424);
884                         Logger::info('Done');
885                         return true;
886                 }
887
888                 return false;
889         }
890
891         /**
892          * update the "uri-id" field in the fcontact table
893          *
894          * @return bool "true" when the job is done
895          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
896          * @throws \ImagickException
897          */
898         private static function update1425()
899         {
900                 // Was the script completed?
901                 if (DI::keyValue()->get('post_update_version') >= 1425) {
902                         return true;
903                 }
904
905                 if (!DBStructure::existsTable('fcontact')) {
906                         DI::keyValue()->set('post_update_version', 1425);
907                         return true;
908                 }
909
910                 $condition = ["`uri-id` IS NULL"];
911                 Logger::info('Start', ['rest' => DBA::count('fcontact', $condition)]);
912
913                 $rows = 0;
914                 $fcontacts = DBA::select('fcontact', ['id', 'url', 'guid'], $condition, ['limit' => 1000]);
915
916                 if (DBA::errorNo() != 0) {
917                         Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
918                         return false;
919                 }
920
921                 while ($fcontact = DBA::fetch($fcontacts)) {
922                         if (!empty($fcontact['guid'])) {
923                                 $uriid = ItemURI::insert(['uri' => $fcontact['url'], 'guid' => $fcontact['guid']]);
924                         } else {
925                                 $uriid = ItemURI::getIdByURI($fcontact['url']);
926                         }
927                         DBA::update('fcontact', ['uri-id' => $uriid], ['id' => $fcontact['id']]);
928                         ++$rows;
929                 }
930                 DBA::close($fcontacts);
931
932                 Logger::info('Processed', ['rows' => $rows]);
933
934                 if ($rows <= 100) {
935                         DI::keyValue()->set('post_update_version', 1425);
936                         Logger::info('Done');
937                         return true;
938                 }
939
940                 return false;
941         }
942
943         /**
944          * update the "uri-id" field in the apcontact table
945          *
946          * @return bool "true" when the job is done
947          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
948          * @throws \ImagickException
949          */
950         private static function update1426()
951         {
952                 // Was the script completed?
953                 if (DI::keyValue()->get('post_update_version') >= 1426) {
954                         return true;
955                 }
956
957                 $condition = ["`uri-id` IS NULL"];
958                 Logger::info('Start', ['rest' => DBA::count('apcontact', $condition)]);
959
960                 $rows = 0;
961                 $apcontacts = DBA::select('apcontact', ['url', 'uuid'], $condition, ['limit' => 1000]);
962
963                 if (DBA::errorNo() != 0) {
964                         Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
965                         return false;
966                 }
967
968                 while ($apcontact = DBA::fetch($apcontacts)) {
969                         if (!empty($apcontact['uuid'])) {
970                                 $uriid = ItemURI::insert(['uri' => $apcontact['url'], 'guid' => $apcontact['uuid']]);
971                         } else {
972                                 $uriid = ItemURI::getIdByURI($apcontact['url']);
973                         }
974                         DBA::update('apcontact', ['uri-id' => $uriid], ['url' => $apcontact['url']]);
975                         ++$rows;
976                 }
977                 DBA::close($apcontacts);
978
979                 Logger::info('Processed', ['rows' => $rows]);
980
981                 if ($rows <= 100) {
982                         DI::keyValue()->set('post_update_version', 1426);
983                         Logger::info('Done');
984                         return true;
985                 }
986
987                 return false;
988         }
989
990         /**
991          * update the "uri-id" field in the event table
992          *
993          * @return bool "true" when the job is done
994          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
995          * @throws \ImagickException
996          */
997         private static function update1427()
998         {
999                 // Was the script completed?
1000                 if (DI::keyValue()->get('post_update_version') >= 1427) {
1001                         return true;
1002                 }
1003
1004                 $condition = ["`uri-id` IS NULL"];
1005                 Logger::info('Start', ['rest' => DBA::count('event', $condition)]);
1006
1007                 $rows = 0;
1008                 $events = DBA::select('event', ['id', 'uri', 'guid'], $condition, ['limit' => 1000]);
1009
1010                 if (DBA::errorNo() != 0) {
1011                         Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
1012                         return false;
1013                 }
1014
1015                 while ($event = DBA::fetch($events)) {
1016                         if (!empty($event['guid'])) {
1017                                 $uriid = ItemURI::insert(['uri' => $event['uri'], 'guid' => $event['guid']]);
1018                         } else {
1019                                 $uriid = ItemURI::getIdByURI($event['uri']);
1020                         }
1021                         DBA::update('event', ['uri-id' => $uriid], ['id' => $event['id']]);
1022                         ++$rows;
1023                 }
1024                 DBA::close($events);
1025
1026                 Logger::info('Processed', ['rows' => $rows]);
1027
1028                 if ($rows <= 100) {
1029                         DI::keyValue()->set('post_update_version', 1427);
1030                         Logger::info('Done');
1031                         return true;
1032                 }
1033
1034                 return false;
1035         }
1036
1037         /**
1038          * Fill the receivers of the post via the raw source
1039          *
1040          * @return bool "true" when the job is done
1041          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
1042          * @throws \ImagickException
1043          */
1044         private static function update1452()
1045         {
1046                 // Was the script completed?
1047                 if (DI::keyValue()->get('post_update_version') >= 1452) {
1048                         return true;
1049                 }
1050
1051                 if (!DBStructure::existsTable('conversation')) {
1052                         DI::keyValue()->set('post_update_version', 1452);
1053                         return true;
1054                 }
1055
1056                 $id = DI::keyValue()->get('post_update_version_1452_id') ?? 0;
1057
1058                 Logger::info('Start', ['uri-id' => $id]);
1059
1060                 $rows     = 0;
1061                 $received = '';
1062
1063                 $conversations = DBA::p("SELECT `post-view`.`uri-id`, `conversation`.`source`, `conversation`.`received` FROM `conversation`
1064                         INNER JOIN `post-view` ON `post-view`.`uri` = `conversation`.`item-uri`
1065                         WHERE NOT `source` IS NULL AND `conversation`.`protocol` = ? AND `uri-id` > ? LIMIT ?",
1066                         Conversation::PARCEL_ACTIVITYPUB, $id, 1000);
1067
1068                 if (DBA::errorNo() != 0) {
1069                         Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
1070                         return false;
1071                 }
1072
1073                 while ($conversation = DBA::fetch($conversations)) {
1074                         $id       = $conversation['uri-id'];
1075                         $received = $conversation['received'];
1076
1077                         $raw = json_decode($conversation['source'], true);
1078                         if (empty($raw)) {
1079                                 continue;
1080                         }
1081                         $activity = JsonLD::compact($raw);
1082
1083                         $urls = Receiver::getReceiverURL($activity);
1084                         Processor::storeReceivers($conversation['uri-id'], $urls);
1085
1086                         if (!empty($activity['as:object'])) {
1087                                 $urls = array_merge($urls, Receiver::getReceiverURL($activity['as:object']));
1088                                 Processor::storeReceivers($conversation['uri-id'], $urls);
1089                         }
1090                         ++$rows;
1091                 }
1092
1093                 DBA::close($conversations);
1094
1095                 DI::keyValue()->set('post_update_version_1452_id', $id);
1096
1097                 Logger::info('Processed', ['rows' => $rows, 'last' => $id, 'last-received' => $received]);
1098
1099                 if ($rows <= 100) {
1100                         DI::keyValue()->set('post_update_version', 1452);
1101                         Logger::info('Done');
1102                         return true;
1103                 }
1104
1105                 return false;
1106         }
1107
1108         /**
1109          * Correct the parent.
1110          * This fixes a bug that was introduced in the development of version 2022.09
1111          *
1112          * @return bool "true" when the job is done
1113          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
1114          * @throws \ImagickException
1115          */
1116         private static function update1483()
1117         {
1118                 // Was the script completed?
1119                 if (DI::keyValue()->get('post_update_version') >= 1483) {
1120                         return true;
1121                 }
1122
1123                 Logger::info('Start');
1124
1125                 $posts = DBA::select('post-view', ['uri-id'], ['conversation' => './']);
1126                 while ($post = DBA::fetch($posts)) {
1127                         $parent = Item::getParent($post['uri-id']);
1128                         if ($parent != 0) {
1129                                 DBA::update('post', ['parent-uri-id' => $parent], ['uri-id' => $post['uri-id']]);
1130                                 DBA::update('post-user', ['parent-uri-id' => $parent], ['uri-id' => $post['uri-id']]);
1131                         }
1132                 }
1133                 DBA::close($posts);
1134
1135                 DI::keyValue()->set('post_update_version', 1483);
1136                 Logger::info('Done');
1137                 return true;
1138         }
1139
1140         /**
1141          * Handle duplicate contact entries
1142          *
1143          * @return bool "true" when the job is done
1144          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
1145          * @throws \ImagickException
1146          */
1147         private static function update1484()
1148         {
1149                 // Was the script completed?
1150                 if (DI::keyValue()->get('post_update_version') >= 1484) {
1151                         return true;
1152                 }
1153
1154                 $id = DI::keyValue()->get('post_update_version_1484_id') ?? 0;
1155
1156                 Logger::info('Start', ['id' => $id]);
1157
1158                 $rows = 0;
1159
1160                 $contacts = DBA::select('contact', ['id', 'uid', 'uri-id', 'url'], ["`id` > ?", $id], ['order' => ['id'], 'limit' => 1000]);
1161
1162                 if (DBA::errorNo() != 0) {
1163                         Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
1164                         return false;
1165                 }
1166
1167                 while ($contact = DBA::fetch($contacts)) {
1168                         $id = $contact['id'];
1169                         if (is_null($contact['uri-id'])) {
1170                                 $contact['uri-id'] = ItemURI::getIdByURI($contact['url']);
1171                                 DBA::update('contact', ['uri-id' => $contact['uri-id']], ['id' => $contact['id']]);
1172                         }
1173                         Contact::setAccountUser($contact['id'], $contact['uid'], $contact['uri-id'], $contact['url']);
1174                         ++$rows;
1175                 }
1176                 DBA::close($contacts);
1177
1178                 DI::keyValue()->set('post_update_version_1484_id', $id);
1179
1180                 Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
1181
1182                 if ($rows <= 100) {
1183                         DI::keyValue()->set('post_update_version', 1484);
1184                         Logger::info('Done');
1185                         return true;
1186                 }
1187
1188                 return false;
1189         }
1190
1191         /**
1192          * update the "gsid" (global server id) field in the inbox-status table
1193          *
1194          * @return bool "true" when the job is done
1195          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
1196          * @throws \ImagickException
1197          */
1198         private static function update1507()
1199         {
1200                 // Was the script completed?
1201                 if (DI::keyValue()->get('post_update_version') >= 1507) {
1202                         return true;
1203                 }
1204
1205                 $id = DI::keyValue()->get('post_update_version_1507_id') ?? '';
1206
1207                 Logger::info('Start', ['apcontact' => $id]);
1208
1209                 $start_id = $id;
1210                 $rows = 0;
1211                 $condition = ["`url` > ? AND NOT `gsid` IS NULL", $id];
1212                 $params = ['order' => ['url'], 'limit' => 10000];
1213                 $apcontacts = DBA::select('apcontact', ['url', 'gsid', 'sharedinbox', 'inbox'], $condition, $params);
1214
1215                 if (DBA::errorNo() != 0) {
1216                         Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
1217                         return false;
1218                 }
1219
1220                 while ($apcontact = DBA::fetch($apcontacts)) {
1221                         $id = $apcontact['url'];
1222
1223                         $inbox = [$apcontact['inbox']];
1224                         if (!empty($apcontact['sharedinbox'])) {
1225                                 $inbox[] = $apcontact['sharedinbox'];
1226                         }
1227 //                      $condition = DBA::mergeConditions(['url' => $inbox], ["`gsid` IS NULL"]);
1228                         $condition = ['url' => $inbox];
1229                         DBA::update('inbox-status', ['gsid' => $apcontact['gsid'], 'archive' => GServer::isDefunctById($apcontact['gsid'])], $condition);
1230                         ++$rows;
1231                 }
1232                 DBA::close($apcontacts);
1233
1234                 DI::keyValue()->set('post_update_version_1507_id', $id);
1235
1236                 Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
1237
1238                 if ($start_id == $id) {
1239                         DI::keyValue()->set('post_update_version', 1507);
1240                         Logger::info('Done');
1241                         return true;
1242                 }
1243
1244                 return false;
1245         }
1246 }