]> git.mxchange.org Git - friendica.git/blob - src/Model/Post.php
Merge pull request #11655 from Quix0r/fixes/more-type-hints-002
[friendica.git] / src / Model / Post.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\Model;
23
24 use BadMethodCallException;
25 use Friendica\Core\Logger;
26 use Friendica\Core\System;
27 use Friendica\Database\Database;
28 use Friendica\Database\DBA;
29 use Friendica\Database\DBStructure;
30 use Friendica\Protocol\Activity;
31
32 class Post
33 {
34         /**
35          * Insert a new post entry
36          *
37          * @param integer $uri_id
38          * @param array   $fields
39          * @return int    ID of inserted post
40          * @throws \Exception
41          */
42         public static function insert(int $uri_id, array $data = []): int
43         {
44                 if (empty($uri_id)) {
45                         throw new BadMethodCallException('Empty URI_id');
46                 }
47
48                 $fields = DBStructure::getFieldsForTable('post', $data);
49
50                 // Additionally assign the key fields
51                 $fields['uri-id'] = $uri_id;
52
53                 if (!DBA::insert('post', $fields, Database::INSERT_IGNORE)) {
54                         return 0;
55                 }
56
57                 return DBA::lastInsertId();
58         }
59
60         /**
61          * Fetch a single post row
62          *
63          * @param mixed $stmt statement object
64          * @return array|false current row or false
65          * @throws \Exception
66          */
67         public static function fetch($stmt)
68         {
69                 $row = DBA::fetch($stmt);
70
71                 if (!is_array($row)) {
72                         return $row;
73                 }
74
75                 if (array_key_exists('verb', $row)) {
76                         if (in_array($row['verb'], Item::ACTIVITIES)) {
77                                 if (array_key_exists('title', $row)) {
78                                         $row['title'] = '';
79                                 }
80                                 if (array_key_exists('body', $row)) {
81                                         $row['body'] = $row['verb'];
82                                 }
83                                 if (array_key_exists('object', $row)) {
84                                         $row['object'] = '';
85                                 }
86                                 if (array_key_exists('object-type', $row)) {
87                                         $row['object-type'] = Activity\ObjectType::NOTE;
88                                 }
89                         } elseif (in_array($row['verb'], ['', Activity::POST, Activity::SHARE])) {
90                                 // Posts don't have a target - but having tags or files.
91                                 if (array_key_exists('target', $row)) {
92                                         $row['target'] = '';
93                                 }
94                         }
95                 }
96
97                 if (array_key_exists('extid', $row) && is_null($row['extid'])) {
98                         $row['extid'] = '';
99                 }
100
101                 return $row;
102         }
103
104         /**
105          * Fills an array with data from an post query
106          *
107          * @param object $stmt statement object
108          * @param bool   $do_close
109          * @return array Data array
110          * @todo Find proper type-hint for $stmt and maybe avoid boolean
111          */
112         public static function toArray($stmt, bool $do_close = true)
113         {
114                 if (is_bool($stmt)) {
115                         return $stmt;
116                 }
117
118                 $data = [];
119                 while ($row = self::fetch($stmt)) {
120                         $data[] = $row;
121                 }
122                 if ($do_close) {
123                         DBA::close($stmt);
124                 }
125                 return $data;
126         }
127
128         /**
129          * Check if post-user-view records exists
130          *
131          * @param array $condition array of fields for condition
132          *
133          * @return boolean Are there rows for that condition?
134          * @throws \Exception
135          */
136         public static function exists(array $condition): bool
137         {
138                 return DBA::exists('post-user-view', $condition);
139         }
140
141         /**
142          * Counts the post-user-view records satisfying the provided condition
143          *
144          * @param array        $condition array of fields for condition
145          * @param array        $params    Array of several parameters
146          *
147          * @return int
148          *
149          * Example:
150          * $condition = ["uid" => 1, "network" => 'dspr'];
151          * or:
152          * $condition = ["`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr'];
153          *
154          * $count = Post::count($condition);
155          * @throws \Exception
156          */
157         public static function count(array $condition = [], array $params = []): int
158         {
159                 return DBA::count('post-user-view', $condition, $params);
160         }
161
162         /**
163          * Counts the post-thread-user-view records satisfying the provided condition
164          *
165          * @param array        $condition array of fields for condition
166          * @param array        $params    Array of several parameters
167          *
168          * @return int
169          *
170          * Example:
171          * $condition = ["uid" => 1, "network" => 'dspr'];
172          * or:
173          * $condition = ["`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr'];
174          *
175          * $count = Post::count($condition);
176          * @throws \Exception
177          */
178         public static function countThread(array $condition = [], array $params = []): int
179         {
180                 return DBA::count('post-thread-user-view', $condition, $params);
181         }
182
183         /**
184          * Counts the post-view records satisfying the provided condition
185          *
186          * @param array        $condition array of fields for condition
187          * @param array        $params    Array of several parameters
188          *
189          * @return int
190          *
191          * Example:
192          * $condition = ["network" => 'dspr'];
193          * or:
194          * $condition = ["`network` IN (?, ?)", 1, 'dfrn', 'dspr'];
195          *
196          * $count = Post::count($condition);
197          * @throws \Exception
198          */
199         public static function countPosts(array $condition = [], array $params = []): int
200         {
201                 return DBA::count('post-view', $condition, $params);
202         }
203
204         /**
205          * Retrieve a single record from the post-user-view view and returns it in an associative array
206          *
207          * @param array $fields
208          * @param array $condition
209          * @param array $params
210          * @param bool  $user_mode true = post-user-view, false = post-view
211          * @return bool|array
212          * @throws \Exception
213          * @see   DBA::select
214          */
215         public static function selectFirst(array $fields = [], array $condition = [], array $params = [])
216         {
217                 $params['limit'] = 1;
218
219                 $result = self::select($fields, $condition, $params);
220
221                 if (is_bool($result)) {
222                         return $result;
223                 } else {
224                         $row = self::fetch($result);
225                         DBA::close($result);
226                         return $row;
227                 }
228         }
229
230         /**
231          * Retrieve a single record from the post-view view and returns it in an associative array
232          *
233          * @param array $fields
234          * @param array $condition
235          * @param array $params
236          * @return bool|array
237          * @throws \Exception
238          * @see   DBA::select
239          */
240         public static function selectFirstPost(array $fields = [], array $condition = [], array $params = [])
241         {
242                 $params['limit'] = 1;
243
244                 $result = self::selectPosts($fields, $condition, $params);
245
246                 if (is_bool($result)) {
247                         return $result;
248                 } else {
249                         $row = self::fetch($result);
250                         DBA::close($result);
251                         return $row;
252                 }
253         }
254
255         /**
256          * Retrieve a single record from the post-thread-user-view view and returns it in an associative array
257          *
258          * @param array $fields
259          * @param array $condition
260          * @param array $params
261          * @return bool|array
262          * @throws \Exception
263          * @see   DBA::select
264          */
265         public static function selectFirstThread(array $fields = [], array $condition = [], array $params = [])
266         {
267                 $params['limit'] = 1;
268
269                 $result = self::selectThread($fields, $condition, $params);
270
271                 if (is_bool($result)) {
272                         return $result;
273                 } else {
274                         $row = self::fetch($result);
275                         DBA::close($result);
276                         return $row;
277                 }
278         }
279
280         /**
281          * Select rows from the post-user-view view and returns them as an array
282          *
283          * @param array $selected  Array of selected fields, empty for all
284          * @param array $condition Array of fields for condition
285          * @param array $params    Array of several parameters
286          *
287          * @return array
288          * @throws \Exception
289          */
290         public static function selectToArray(array $fields = [], array $condition = [], array $params = [])
291         {
292                 $result = self::select($fields, $condition, $params);
293
294                 if (is_bool($result)) {
295                         return [];
296                 }
297
298                 $data = [];
299                 while ($row = self::fetch($result)) {
300                         $data[] = $row;
301                 }
302                 DBA::close($result);
303
304                 return $data;
305         }
306
307         /**
308          * Select rows from the given view
309          *
310          * @param string $view      View (post-user-view or post-thread-user-view)
311          * @param array  $selected  Array of selected fields, empty for all
312          * @param array  $condition Array of fields for condition
313          * @param array  $params    Array of several parameters
314          *
315          * @return boolean|object
316          * @throws \Exception
317          */
318         private static function selectView(string $view, array $selected = [], array $condition = [], array $params = [])
319         {
320                 if (empty($selected)) {
321                         $selected = array_merge(Item::DISPLAY_FIELDLIST, Item::ITEM_FIELDLIST);
322
323                         if ($view == 'post-thread-user-view') {
324                                 $selected = array_merge($selected, ['ignored']);
325                         }
326                 }
327
328                 $selected = array_unique($selected);
329
330                 return DBA::select($view, $selected, $condition, $params);
331         }
332
333         /**
334          * Select rows from the post-user-view view
335          *
336          * @param array $selected  Array of selected fields, empty for all
337          * @param array $condition Array of fields for condition
338          * @param array $params    Array of several parameters
339          *
340          * @return boolean|object
341          * @throws \Exception
342          */
343         public static function select(array $selected = [], array $condition = [], array $params = [])
344         {
345                 return self::selectView('post-user-view', $selected, $condition, $params);
346         }
347
348         /**
349          * Select rows from the post-view view
350          *
351          * @param array $selected  Array of selected fields, empty for all
352          * @param array $condition Array of fields for condition
353          * @param array $params    Array of several parameters
354          *
355          * @return boolean|object
356          * @throws \Exception
357          */
358         public static function selectPosts(array $selected = [], array $condition = [], array $params = [])
359         {
360                 return self::selectView('post-view', $selected, $condition, $params);
361         }
362
363         /**
364          * Select rows from the post-thread-user-view view
365          *
366          * @param array $selected  Array of selected fields, empty for all
367          * @param array $condition Array of fields for condition
368          * @param array $params    Array of several parameters
369          *
370          * @return boolean|object
371          * @throws \Exception
372          */
373         public static function selectThread(array $selected = [], array $condition = [], array $params = [])
374         {
375                 return self::selectView('post-thread-user-view', $selected, $condition, $params);
376         }
377
378         /**
379          * Select rows from the given view for a given user
380          *
381          * @param string  $view      View (post-user-view or post-thread-user-view)
382          * @param integer $uid       User ID
383          * @param array   $selected  Array of selected fields, empty for all
384          * @param array   $condition Array of fields for condition
385          * @param array   $params    Array of several parameters
386          *
387          * @return boolean|object
388          * @throws \Exception
389          */
390         private static function selectViewForUser(string $view, int $uid, array $selected = [], array $condition = [], array $params = [])
391         {
392                 if (empty($selected)) {
393                         $selected = Item::DISPLAY_FIELDLIST;
394                 }
395
396                 $condition = DBA::mergeConditions($condition,
397                         ["`visible` AND NOT `deleted`
398                         AND NOT `author-blocked` AND NOT `owner-blocked`
399                         AND (NOT `causer-blocked` OR `causer-id` = ? OR `causer-id` IS NULL) AND NOT `contact-blocked`
400                         AND ((NOT `contact-readonly` AND NOT `contact-pending` AND (`contact-rel` IN (?, ?)))
401                                 OR `self` OR `gravity` != ? OR `contact-uid` = ?)
402                         AND NOT EXISTS (SELECT `uri-id` FROM `post-user` WHERE `uid` = ? AND `uri-id` = `" . $view . "`.`uri-id` AND `hidden`)
403                         AND NOT EXISTS (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `author-id` AND `blocked`)
404                         AND NOT EXISTS (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `owner-id` AND `blocked`)
405                         AND NOT EXISTS (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `author-id` AND `ignored` AND `gravity` = ?)
406                         AND NOT EXISTS (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `owner-id` AND `ignored` AND `gravity` = ?)",
407                         0, Contact::SHARING, Contact::FRIEND, GRAVITY_PARENT, 0, $uid, $uid, $uid, $uid, GRAVITY_PARENT, $uid, GRAVITY_PARENT]);
408
409                 $select_string = implode(', ', array_map([DBA::class, 'quoteIdentifier'], $selected));
410
411                 $condition_string = DBA::buildCondition($condition);
412                 $param_string = DBA::buildParameter($params);
413
414                 $sql = "SELECT " . $select_string . " FROM `" . $view . "` " . $condition_string . $param_string;
415                 $sql = DBA::cleanQuery($sql);
416
417                 return DBA::p($sql, $condition);
418         }
419
420         /**
421          * Select rows from the post-user-view view for a given user
422          *
423          * @param integer $uid       User ID
424          * @param array   $selected  Array of selected fields, empty for all
425          * @param array   $condition Array of fields for condition
426          * @param array   $params    Array of several parameters
427          *
428          * @return boolean|object
429          * @throws \Exception
430          */
431         public static function selectForUser(int $uid, array $selected = [], array $condition = [], array $params = [])
432         {
433                 return self::selectViewForUser('post-user-view', $uid, $selected, $condition, $params);
434         }
435
436         /**
437          * Select rows from the post-view view for a given user
438          *
439          * @param integer $uid       User ID
440          * @param array   $selected  Array of selected fields, empty for all
441          * @param array   $condition Array of fields for condition
442          * @param array   $params    Array of several parameters
443          *
444          * @return boolean|object
445          * @throws \Exception
446          */
447         public static function selectPostsForUser(int $uid, array $selected = [], array $condition = [], array $params = [])
448         {
449                 return self::selectViewForUser('post-view', $uid, $selected, $condition, $params);
450         }
451
452         /**
453          * Select rows from the post-thread-user-view view for a given user
454          *
455          * @param integer $uid       User ID
456          * @param array   $selected  Array of selected fields, empty for all
457          * @param array   $condition Array of fields for condition
458          * @param array   $params    Array of several parameters
459          *
460          * @return boolean|object
461          * @throws \Exception
462          */
463         public static function selectThreadForUser(int $uid, array $selected = [], array $condition = [], array $params = [])
464         {
465                 return self::selectViewForUser('post-thread-user-view', $uid, $selected, $condition, $params);
466         }
467
468         /**
469          * Retrieve a single record from the post-user-view view for a given user and returns it in an associative array
470          *
471          * @param integer $uid User ID
472          * @param array   $selected
473          * @param array   $condition
474          * @param array   $params
475          * @return bool|array
476          * @throws \Exception
477          * @see   DBA::select
478          */
479         public static function selectFirstForUser(int $uid, array $selected = [], array $condition = [], array $params = [])
480         {
481                 $params['limit'] = 1;
482
483                 $result = self::selectForUser($uid, $selected, $condition, $params);
484
485                 if (is_bool($result)) {
486                         return $result;
487                 } else {
488                         $row = self::fetch($result);
489                         DBA::close($result);
490                         return $row;
491                 }
492         }
493
494         /**
495          * Update existing post entries
496          *
497          * @param array $fields    The fields that are to be changed
498          * @param array $condition The condition for finding the item entries
499          *
500          * A return value of "0" doesn't mean an error - but that 0 rows had been changed.
501          *
502          * @return integer|boolean number of affected rows - or "false" if there was an error
503          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
504          */
505         public static function update(array $fields, array $condition)
506         {
507                 $affected = 0;
508
509                 Logger::info('Start Update', ['fields' => $fields, 'condition' => $condition, 'uid' => local_user(),'callstack' => System::callstack(10)]);
510
511                 // Don't allow changes to fields that are responsible for the relation between the records
512                 unset($fields['id']);
513                 unset($fields['parent']);
514                 unset($fields['uid']);
515                 unset($fields['uri']);
516                 unset($fields['uri-id']);
517                 unset($fields['thr-parent']);
518                 unset($fields['thr-parent-id']);
519                 unset($fields['parent-uri']);
520                 unset($fields['parent-uri-id']);
521
522                 $thread_condition = DBA::mergeConditions($condition, ['gravity' => GRAVITY_PARENT]);
523
524                 // To ensure the data integrity we do it in an transaction
525                 DBA::transaction();
526
527                 $update_fields = DBStructure::getFieldsForTable('post-user', $fields);
528                 if (!empty($update_fields)) {
529                         $affected_count = 0;
530                         $posts = DBA::select('post-user-view', ['post-user-id'], $condition);
531                         while ($rows = DBA::toArray($posts, false, 100)) {
532                                 $puids = array_column($rows, 'post-user-id');
533                                 if (!DBA::update('post-user', $update_fields, ['id' => $puids])) {
534                                         DBA::rollback();
535                                         Logger::notice('Updating post-user failed', ['fields' => $update_fields, 'condition' => $condition]);
536                                         return false;
537                                 }
538                                 $affected_count += DBA::affectedRows();
539                         }
540                         DBA::close($posts);
541                         $affected = $affected_count;
542                 }
543
544                 $update_fields = DBStructure::getFieldsForTable('post-content', $fields);
545                 if (!empty($update_fields)) {
546                         $affected_count = 0;
547                         $posts = DBA::select('post-user-view', ['uri-id'], $condition, ['group_by' => ['uri-id']]);
548                         while ($rows = DBA::toArray($posts, false, 100)) {
549                                 $uriids = array_column($rows, 'uri-id');
550                                 if (!DBA::update('post-content', $update_fields, ['uri-id' => $uriids])) {
551                                         DBA::rollback();
552                                         Logger::notice('Updating post-content failed', ['fields' => $update_fields, 'condition' => $condition]);
553                                         return false;
554                                 }
555                                 $affected_count += DBA::affectedRows();
556                         }
557                         DBA::close($posts);
558                         $affected = max($affected, $affected_count);
559                 }
560
561                 $update_fields = DBStructure::getFieldsForTable('post', $fields);
562                 if (!empty($update_fields)) {
563                         $affected_count = 0;
564                         $posts = DBA::select('post-user-view', ['uri-id'], $condition, ['group_by' => ['uri-id']]);
565                         while ($rows = DBA::toArray($posts, false, 100)) {
566                                 $uriids = array_column($rows, 'uri-id');
567                                 if (!DBA::update('post', $update_fields, ['uri-id' => $uriids])) {
568                                         DBA::rollback();
569                                         Logger::notice('Updating post failed', ['fields' => $update_fields, 'condition' => $condition]);
570                                         return false;
571                                 }
572                                 $affected_count += DBA::affectedRows();
573                         }
574                         DBA::close($posts);
575                         $affected = max($affected, $affected_count);
576                 }
577
578                 $update_fields = Post\DeliveryData::extractFields($fields);
579                 if (!empty($update_fields)) {
580                         $affected_count = 0;
581                         $posts = DBA::select('post-user-view', ['uri-id'], $condition, ['group_by' => ['uri-id']]);
582                         while ($rows = DBA::toArray($posts, false, 100)) {
583                                 $uriids = array_column($rows, 'uri-id');
584                                 if (!DBA::update('post-delivery-data', $update_fields, ['uri-id' => $uriids])) {
585                                         DBA::rollback();
586                                         Logger::notice('Updating post-delivery-data failed', ['fields' => $update_fields, 'condition' => $condition]);
587                                         return false;
588                                 }
589                                 $affected_count += DBA::affectedRows();
590                         }
591                         DBA::close($posts);
592                         $affected = max($affected, $affected_count);
593                 }
594
595                 $update_fields = DBStructure::getFieldsForTable('post-thread', $fields);
596                 if (!empty($update_fields)) {
597                         $affected_count = 0;
598                         $posts = DBA::select('post-user-view', ['uri-id'], $thread_condition, ['group_by' => ['uri-id']]);
599                         while ($rows = DBA::toArray($posts, false, 100)) {
600                                 $uriids = array_column($rows, 'uri-id');
601                                 if (!DBA::update('post-thread', $update_fields, ['uri-id' => $uriids])) {
602                                         DBA::rollback();
603                                         Logger::notice('Updating post-thread failed', ['fields' => $update_fields, 'condition' => $condition]);
604                                         return false;
605                                 }
606                                 $affected_count += DBA::affectedRows();
607                         }
608                         DBA::close($posts);
609                         $affected = max($affected, $affected_count);
610                 }
611
612                 $update_fields = DBStructure::getFieldsForTable('post-thread-user', $fields);
613                 if (!empty($update_fields)) {
614                         $affected_count = 0;
615                         $posts = DBA::select('post-user-view', ['post-user-id'], $thread_condition);
616                         while ($rows = DBA::toArray($posts, false, 100)) {
617                                 $thread_puids = array_column($rows, 'post-user-id');
618                                 if (!DBA::update('post-thread-user', $update_fields, ['post-user-id' => $thread_puids])) {
619                                         DBA::rollback();
620                                         Logger::notice('Updating post-thread-user failed', ['fields' => $update_fields, 'condition' => $condition]);
621                                         return false;
622                                 }
623                                 $affected_count += DBA::affectedRows();
624                         }
625                         DBA::close($posts);
626                         $affected = max($affected, $affected_count);
627                 }
628
629                 DBA::commit();
630
631                 Logger::info('Updated posts', ['rows' => $affected]);
632                 return $affected;
633         }
634
635         /**
636          * Delete a row from the post table
637          *
638          * @param array        $conditions Field condition(s)
639          * @param array        $options
640          *                           - cascade: If true we delete records in other tables that depend on the one we're deleting through
641          *                           relations (default: true)
642          *
643          * @return boolean was the delete successful?
644          * @throws \Exception
645          */
646         public static function delete(array $conditions, array $options = []): bool
647         {
648                 return DBA::delete('post', $conditions, $options);
649         }
650 }