]> git.mxchange.org Git - friendica.git/blob - src/Model/Post.php
Merge pull request #9882 from MrPetovan/bug/po2php-plural-conversion
[friendica.git] / src / Model / Post.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2020, Friendica
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 Friendica\Core\Logger;
25 use Friendica\Database\DBA;
26 use Friendica\Database\DBStructure;
27 use Friendica\Protocol\Activity;
28
29 class Post
30 {
31         /**
32          * Fetch a single post row
33          *
34          * @param mixed $stmt statement object
35          * @return array|false current row or false
36          * @throws \Exception
37          */
38         public static function fetch($stmt)
39         {
40                 $row = DBA::fetch($stmt);
41
42                 if (!is_array($row)) {
43                         return $row;
44                 }
45
46                 if (array_key_exists('verb', $row)) {
47                         if (in_array($row['verb'], Item::ACTIVITIES)) {
48                                 if (array_key_exists('title', $row)) {
49                                         $row['title'] = '';
50                                 }
51                                 if (array_key_exists('body', $row)) {
52                                         $row['body'] = $row['verb'];
53                                 }
54                                 if (array_key_exists('object', $row)) {
55                                         $row['object'] = '';
56                                 }
57                                 if (array_key_exists('object-type', $row)) {
58                                         $row['object-type'] = Activity\ObjectType::NOTE;
59                                 }
60                         } elseif (in_array($row['verb'], ['', Activity::POST, Activity::SHARE])) {
61                                 // Posts don't have a target - but having tags or files.
62                                 if (array_key_exists('target', $row)) {
63                                         $row['target'] = '';
64                                 }
65                         }
66                 }
67
68                 return $row;
69         }
70
71         /**
72          * Fills an array with data from an post query
73          *
74          * @param object $stmt statement object
75          * @param bool   $do_close
76          * @return array Data array
77          */
78         public static function toArray($stmt, $do_close = true) {
79                 if (is_bool($stmt)) {
80                         return $stmt;
81                 }
82
83                 $data = [];
84                 while ($row = self::fetch($stmt)) {
85                         $data[] = $row;
86                 }
87                 if ($do_close) {
88                         DBA::close($stmt);
89                 }
90                 return $data;
91         }
92
93         /**
94          * Check if post data exists
95          *
96          * @param array $condition array of fields for condition
97          *
98          * @return boolean Are there rows for that condition?
99          * @throws \Exception
100          */
101         public static function exists($condition) {
102                 return DBA::exists('post-view', $condition);
103         }
104
105         /**
106          * Counts the posts satisfying the provided condition
107          *
108          * @param array        $condition array of fields for condition
109          * @param array        $params    Array of several parameters
110          *
111          * @return int
112          *
113          * Example:
114          * $condition = ["uid" => 1, "network" => 'dspr'];
115          * or:
116          * $condition = ["`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr'];
117          *
118          * $count = Post::count($condition);
119          * @throws \Exception
120          */
121         public static function count(array $condition = [], array $params = [])
122         {
123                 return DBA::count('post-view', $condition, $params);
124         }
125
126         /**
127          * Retrieve a single record from the post table and returns it in an associative array
128          *
129          * @param array $fields
130          * @param array $condition
131          * @param array $params
132          * @return bool|array
133          * @throws \Exception
134          * @see   DBA::select
135          */
136         public static function selectFirst(array $fields = [], array $condition = [], $params = [])
137         {
138                 $params['limit'] = 1;
139
140                 $result = self::select($fields, $condition, $params);
141
142                 if (is_bool($result)) {
143                         return $result;
144                 } else {
145                         $row = self::fetch($result);
146                         DBA::close($result);
147                         return $row;
148                 }
149         }
150
151         /**
152          * Select rows from the post table and returns them as an array
153          *
154          * @param array $selected  Array of selected fields, empty for all
155          * @param array $condition Array of fields for condition
156          * @param array $params    Array of several parameters
157          *
158          * @return array
159          * @throws \Exception
160          */
161         public static function selectToArray(array $fields = [], array $condition = [], $params = [])
162         {
163                 $result = self::select($fields, $condition, $params);
164
165                 if (is_bool($result)) {
166                         return [];
167                 }
168
169                 $data = [];
170                 while ($row = self::fetch($result)) {
171                         $data[] = $row;
172                 }
173                 DBA::close($result);
174
175                 return $data;
176         }
177
178         /**
179          * Select rows from the given view
180          *
181          * @param string $view      View (post-view or post-thread-view)
182          * @param array  $selected  Array of selected fields, empty for all
183          * @param array  $condition Array of fields for condition
184          * @param array  $params    Array of several parameters
185          *
186          * @return boolean|object
187          * @throws \Exception
188          */
189         private static function selectView(string $view, array $selected = [], array $condition = [], $params = [])
190         {
191                 if (empty($selected)) {
192                         $selected = array_merge(['author-addr', 'author-nick', 'owner-addr', 'owner-nick', 'causer-addr', 'causer-nick',
193                                 'causer-network', 'photo', 'name-date', 'uri-date', 'avatar-date', 'thumb', 'dfrn-id',
194                                 'parent-guid', 'parent-network', 'parent-author-id', 'parent-author-link', 'parent-author-name',
195                                 'parent-author-network', 'signed_text'], Item::DISPLAY_FIELDLIST, Item::ITEM_FIELDLIST, Item::CONTENT_FIELDLIST);
196                         
197                         if ($view == 'post-thread-view') {
198                                 $selected = array_merge($selected, ['ignored', 'iid']);
199                         }
200                 }
201
202                 $selected = array_unique($selected);
203
204                 return DBA::select($view, $selected, $condition, $params);
205         }
206
207         /**
208          * Select rows from the post table
209          *
210          * @param array $selected  Array of selected fields, empty for all
211          * @param array $condition Array of fields for condition
212          * @param array $params    Array of several parameters
213          *
214          * @return boolean|object
215          * @throws \Exception
216          */
217         public static function select(array $selected = [], array $condition = [], $params = [])
218         {
219                 return self::selectView('post-view', $selected, $condition, $params);
220         }
221
222         /**
223          * Select rows from the post table
224          *
225          * @param array $selected  Array of selected fields, empty for all
226          * @param array $condition Array of fields for condition
227          * @param array $params    Array of several parameters
228          *
229          * @return boolean|object
230          * @throws \Exception
231          */
232         public static function selectThread(array $selected = [], array $condition = [], $params = [])
233         {
234                 return self::selectView('post-thread-view', $selected, $condition, $params);
235         }
236
237         /**
238          * Select rows from the given view for a given user
239          *
240          * @param string  $view      View (post-view or post-thread-view)
241          * @param integer $uid       User ID
242          * @param array   $selected  Array of selected fields, empty for all
243          * @param array   $condition Array of fields for condition
244          * @param array   $params    Array of several parameters
245          *
246          * @return boolean|object
247          * @throws \Exception
248          */
249         private static function selectViewForUser(string $view, $uid, array $selected = [], array $condition = [], $params = [])
250         {
251                 if (empty($selected)) {
252                         $selected = Item::DISPLAY_FIELDLIST;
253                 }
254
255                 $condition = DBA::mergeConditions($condition,
256                         ["`visible` AND NOT `deleted` AND NOT `moderated`
257                         AND NOT `author-blocked` AND NOT `owner-blocked`
258                         AND (NOT `causer-blocked` OR `causer-id` = ?) AND NOT `contact-blocked`
259                         AND ((NOT `contact-readonly` AND NOT `contact-pending` AND (`contact-rel` IN (?, ?)))
260                                 OR `self` OR `gravity` != ? OR `contact-uid` = ?)
261                         AND NOT EXISTS (SELECT `iid` FROM `user-item` WHERE `hidden` AND `iid` = `id` AND `uid` = ?)
262                         AND NOT EXISTS (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `author-id` AND `blocked`)
263                         AND NOT EXISTS (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `owner-id` AND `blocked`)
264                         AND NOT EXISTS (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `author-id` AND `ignored` AND `gravity` = ?)
265                         AND NOT EXISTS (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `owner-id` AND `ignored` AND `gravity` = ?)",
266                         0, Contact::SHARING, Contact::FRIEND, GRAVITY_PARENT, 0, $uid, $uid, $uid, $uid, GRAVITY_PARENT, $uid, GRAVITY_PARENT]);
267
268                 $select_string = '';
269
270                 if (in_array('pinned', $selected)) {
271                         $selected = array_flip($selected);
272                         unset($selected['pinned']);
273                         $selected = array_flip($selected);      
274
275                         $select_string = "(SELECT `pinned` FROM `user-item` WHERE `iid` = `" . $view . "`.`id` AND uid=`" . $view . "`.`uid`) AS `pinned`, ";
276                 }
277
278                 $select_string .= implode(', ', array_map([DBA::class, 'quoteIdentifier'], $selected));
279
280                 $condition_string = DBA::buildCondition($condition);
281                 $param_string = DBA::buildParameter($params);
282
283                 $sql = "SELECT " . $select_string . " FROM `" . $view . "` " . $condition_string . $param_string;
284                 $sql = DBA::cleanQuery($sql);
285
286                 return DBA::p($sql, $condition);
287         }
288
289         /**
290          * Select rows from the post view for a given user
291          *
292          * @param integer $uid       User ID
293          * @param array   $selected  Array of selected fields, empty for all
294          * @param array   $condition Array of fields for condition
295          * @param array   $params    Array of several parameters
296          *
297          * @return boolean|object
298          * @throws \Exception
299          */
300         public static function selectForUser($uid, array $selected = [], array $condition = [], $params = [])
301         {
302                 return self::selectViewForUser('post-view', $uid, $selected, $condition, $params);
303         }
304
305                 /**
306          * Select rows from the post view for a given user
307          *
308          * @param integer $uid       User ID
309          * @param array   $selected  Array of selected fields, empty for all
310          * @param array   $condition Array of fields for condition
311          * @param array   $params    Array of several parameters
312          *
313          * @return boolean|object
314          * @throws \Exception
315          */
316         public static function selectThreadForUser($uid, array $selected = [], array $condition = [], $params = [])
317         {
318                 return self::selectViewForUser('post-thread-view', $uid, $selected, $condition, $params);
319         }
320
321         /**
322          * Retrieve a single record from the post view for a given user and returns it in an associative array
323          *
324          * @param integer $uid User ID
325          * @param array   $selected
326          * @param array   $condition
327          * @param array   $params
328          * @return bool|array
329          * @throws \Exception
330          * @see   DBA::select
331          */
332         public static function selectFirstForUser($uid, array $selected = [], array $condition = [], $params = [])
333         {
334                 $params['limit'] = 1;
335
336                 $result = self::selectForUser($uid, $selected, $condition, $params);
337
338                 if (is_bool($result)) {
339                         return $result;
340                 } else {
341                         $row = self::fetch($result);
342                         DBA::close($result);
343                         return $row;
344                 }
345         }
346
347         /**
348          * Retrieve a single record from the starting post in the item table and returns it in an associative array
349          *
350          * @param integer $uid User ID
351          * @param array   $selected
352          * @param array   $condition
353          * @param array   $params
354          * @return bool|array
355          * @throws \Exception
356          * @see   DBA::select
357          */
358         public static function selectFirstThreadForUser($uid, array $selected = [], array $condition = [], $params = [])
359         {
360                 $params['limit'] = 1;
361
362                 $result = self::selectThreadForUser($uid, $selected, $condition, $params);
363
364                 if (is_bool($result)) {
365                         return $result;
366                 } else {
367                         $row = self::fetch($result);
368                         DBA::close($result);
369                         return $row;
370                 }
371         }
372
373         /**
374          * Select pinned rows from the item table for a given user
375          *
376          * @param integer $uid       User ID
377          * @param array   $selected  Array of selected fields, empty for all
378          * @param array   $condition Array of fields for condition
379          * @param array   $params    Array of several parameters
380          *
381          * @return boolean|object
382          * @throws \Exception
383          */
384         public static function selectPinned(int $uid, array $selected = [], array $condition = [], $params = [])
385         {
386                 $useritems = DBA::select('user-item', ['iid'], ['uid' => $uid, 'pinned' => true]);
387                 if (!DBA::isResult($useritems)) {
388                         return $useritems;
389                 }
390
391                 $pinned = [];
392                 while ($useritem = DBA::fetch($useritems)) {
393                         $pinned[] = $useritem['iid'];
394                 }
395                 DBA::close($useritems);
396
397                 if (empty($pinned)) {
398                         return [];
399                 }
400
401                 $condition = DBA::mergeConditions(['iid' => $pinned], $condition);
402
403                 return self::selectThreadForUser($uid, $selected, $condition, $params);
404         }
405
406         /**
407          * Update existing post entries
408          *
409          * @param array $fields    The fields that are to be changed
410          * @param array $condition The condition for finding the item entries
411          *
412          * A return value of "0" doesn't mean an error - but that 0 rows had been changed.
413          *
414          * @return integer|boolean number of affected rows - or "false" if there was an error
415          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
416          */
417         public static function update(array $fields, array $condition)
418         {
419                 $affected = 0;
420
421                 Logger::info('Start Update', ['fields' => $fields, 'condition' => $condition]);
422
423                 // Don't allow changes to fields that are responsible for the relation between the records
424                 unset($fields['id']);
425                 unset($fields['parent']);
426                 unset($fields['uid']);
427                 unset($fields['uri']);
428                 unset($fields['uri-id']);
429                 unset($fields['thr-parent']);
430                 unset($fields['thr-parent-id']);
431                 unset($fields['parent-uri']);
432                 unset($fields['parent-uri-id']);
433
434                 // To ensure the data integrity we do it in an transaction
435                 DBA::transaction();
436
437                 $update_fields = DBStructure::getFieldsForTable('post-user', $fields);
438                 if (!empty($update_fields)) {
439                         $rows = DBA::selectToArray('post-view', ['post-user-id'], $condition);
440                         $puids = array_column($rows, 'post-user-id');
441                         if (!DBA::update('post-user', $update_fields, ['id' => $puids])) {
442                                 DBA::rollback();
443                                 Logger::notice('Updating post-user failed', ['fields' => $update_fields, 'condition' => $condition]);
444                                 return false;
445                         }
446                         $affected = DBA::affectedRows();                        
447                 }
448
449                 $update_fields = DBStructure::getFieldsForTable('post-content', $fields);
450                 if (!empty($update_fields)) {
451                         $rows = DBA::selectToArray('post-view', ['uri-id'], $condition, ['group_by' => ['uri-id']]);
452                         $uriids = array_column($rows, 'uri-id');
453                         if (!DBA::update('post-content', $update_fields, ['uri-id' => $uriids])) {
454                                 DBA::rollback();
455                                 Logger::notice('Updating post-content failed', ['fields' => $update_fields, 'condition' => $condition]);
456                                 return false;
457                         }
458                         $affected = max($affected, DBA::affectedRows());
459                 }
460
461                 $update_fields = Post\DeliveryData::extractFields($fields);
462                 if (!empty($update_fields)) {
463                         if (empty($uriids)) {
464                                 $rows = DBA::selectToArray('post-view', ['uri-id'], $condition, ['group_by' => ['uri-id']]);
465                                 $uriids = array_column($rows, 'uri-id');
466                         }
467                         if (!DBA::update('post-delivery-data', $update_fields, ['uri-id' => $uriids])) {
468                                 DBA::rollback();
469                                 Logger::notice('Updating post-delivery-data failed', ['fields' => $update_fields, 'condition' => $condition]);
470                                 return false;
471                         }
472                         $affected = max($affected, DBA::affectedRows());
473                 }
474
475                 $update_fields = DBStructure::getFieldsForTable('thread', $fields);
476                 if (!empty($update_fields)) {
477                         $rows = DBA::selectToArray('post-view', ['id'], $condition);
478                         $ids = array_column($rows, 'id');
479                         if (!DBA::update('thread', $update_fields, ['iid' => $ids])) {
480                                 DBA::rollback();
481                                 Logger::notice('Updating thread failed', ['fields' => $update_fields, 'condition' => $condition]);
482                                 return false;
483                         }
484                         $affected = max($affected, DBA::affectedRows());
485                 }
486
487                 $item_fields = ['guid', 'type', 'wall', 'gravity', 'extid', 'created', 'edited', 'commented', 'received', 'changed',
488                         'resource-id', 'post-type', 'private', 'pubmail', 'moderated', 'visible', 'starred', 'bookmark',
489                         'unseen', 'deleted', 'origin', 'forum_mode', 'mention', 'global', 'network', 'vid', 'psid',
490                         'contact-id', 'author-id', 'owner-id', 'causer-id', 'event-id'];
491
492                 $update_fields = [];
493                 foreach ($item_fields as $field) {
494                         if (array_key_exists($field, $fields)) {
495                                 $update_fields[$field] = $fields[$field];
496                         }
497                 }
498                 if (!empty($update_fields)) {
499                         if (empty($ids)) {
500                                 $rows = DBA::selectToArray('post-view', ['id'], $condition, []);
501                                 $ids = array_column($rows, 'id');
502                         }
503                         if (!DBA::update('item', $update_fields, ['id' => $ids])) {
504                                 DBA::rollback();
505                                 Logger::notice('Updating item failed', ['fields' => $update_fields, 'condition' => $condition]);
506                                 return false;
507                         }
508                         $affected = max($affected, DBA::affectedRows());
509                 }
510
511                 DBA::commit();
512
513                 Logger::info('Updated posts', ['rows' => $affected]);
514                 return $affected;
515         }
516 }