]> git.mxchange.org Git - friendica.git/blob - src/Module/Conversation/Network.php
Merge pull request #12459 from MrPetovan/bug/12454-link-preview-translation
[friendica.git] / src / Module / Conversation / Network.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\Module\Conversation;
23
24 use Friendica\BaseModule;
25 use Friendica\Content\BoundariesPager;
26 use Friendica\Content\ForumManager;
27 use Friendica\Content\Nav;
28 use Friendica\Content\Widget;
29 use Friendica\Content\Text\HTML;
30 use Friendica\Core\ACL;
31 use Friendica\Core\Hook;
32 use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
33 use Friendica\Core\Renderer;
34 use Friendica\Core\Session\Capability\IHandleUserSessions;
35 use Friendica\Database\DBA;
36 use Friendica\DI;
37 use Friendica\Model\Contact;
38 use Friendica\Model\Group;
39 use Friendica\Model\Item;
40 use Friendica\Model\Post;
41 use Friendica\Model\Profile;
42 use Friendica\Model\User;
43 use Friendica\Model\Verb;
44 use Friendica\Module\Contact as ModuleContact;
45 use Friendica\Module\Security\Login;
46 use Friendica\Protocol\Activity;
47 use Friendica\Util\DateTimeFormat;
48
49 class Network extends BaseModule
50 {
51         /** @var int */
52         private static $groupId;
53         /** @var int */
54         private static $forumContactId;
55         /** @var string */
56         private static $selectedTab;
57         /** @var mixed */
58         private static $min_id;
59         /** @var mixed */
60         private static $max_id;
61         /** @var string */
62         private static $accountTypeString;
63         /** @var int */
64         private static $accountType;
65         /** @var string */
66         private static $network;
67         /** @var int */
68         private static $itemsPerPage;
69         /** @var string */
70         private static $dateFrom;
71         /** @var string */
72         private static $dateTo;
73         /** @var int */
74         private static $star;
75         /** @var int */
76         private static $mention;
77         /** @var string */
78         protected static $order;
79
80         protected function content(array $request = []): string
81         {
82                 if (!DI::userSession()->getLocalUserId()) {
83                         return Login::form();
84                 }
85
86                 $this->parseRequest($_GET);
87
88                 $module = 'network';
89
90                 DI::page()['aside'] .= Widget::accountTypes($module, self::$accountTypeString);
91                 DI::page()['aside'] .= Group::sidebarWidget($module, $module . '/group', 'standard', self::$groupId);
92                 DI::page()['aside'] .= ForumManager::widget($module . '/forum', DI::userSession()->getLocalUserId(), self::$forumContactId);
93                 DI::page()['aside'] .= Widget::postedByYear($module . '/archive', DI::userSession()->getLocalUserId(), false);
94                 DI::page()['aside'] .= Widget::networks($module, !self::$forumContactId ? self::$network : '');
95                 DI::page()['aside'] .= Widget\SavedSearches::getHTML(DI::args()->getQueryString());
96                 DI::page()['aside'] .= Widget::fileAs('filed', '');
97
98                 $arr = ['query' => DI::args()->getQueryString()];
99                 Hook::callAll('network_content_init', $arr);
100
101                 $o = '';
102
103                 // Fetch a page full of parent items for this page
104                 $params = ['limit' => self::$itemsPerPage];
105                 $table = 'network-thread-view';
106
107                 $items = self::getItems($table, $params);
108
109                 if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'infinite_scroll') && ($_GET['mode'] ?? '') != 'minimal') {
110                         $tpl = Renderer::getMarkupTemplate('infinite_scroll_head.tpl');
111                         $o .= Renderer::replaceMacros($tpl, ['$reload_uri' => DI::args()->getQueryString()]);
112                 }
113
114                 if (!(isset($_GET['mode']) AND ($_GET['mode'] == 'raw'))) {
115                         $o .= self::getTabsHTML(self::$selectedTab);
116
117                         Nav::setSelected(DI::args()->get(0));
118
119                         $content = '';
120
121                         if (self::$forumContactId) {
122                                 // If self::$forumContactId belongs to a communitity forum or a privat goup,.add a mention to the status editor
123                                 $condition = ["`id` = ? AND `contact-type` = ?", self::$forumContactId, Contact::TYPE_COMMUNITY];
124                                 $contact = DBA::selectFirst('contact', ['addr'], $condition);
125                                 if (!empty($contact['addr'])) {
126                                         $content = '!' . $contact['addr'];
127                                 }
128                         }
129
130                         $a = DI::app();
131
132                         $default_permissions = [];
133                         if (self::$groupId) {
134                                 $default_permissions['allow_gid'] = [self::$groupId];
135                         }
136
137                         $allowedCids = [];
138                         if (self::$forumContactId) {
139                                 $allowedCids[] = (int) self::$forumContactId;
140                         } elseif (self::$network) {
141                                 $condition = [
142                                         'uid'     => DI::userSession()->getLocalUserId(),
143                                         'network' => self::$network,
144                                         'self'    => false,
145                                         'blocked' => false,
146                                         'pending' => false,
147                                         'archive' => false,
148                                         'rel'     => [Contact::SHARING, Contact::FRIEND],
149                                 ];
150                                 $contactStmt = DBA::select('contact', ['id'], $condition);
151                                 while ($contact = DBA::fetch($contactStmt)) {
152                                         $allowedCids[] = (int) $contact['id'];
153                                 }
154                                 DBA::close($contactStmt);
155                         }
156
157                         if (count($allowedCids)) {
158                                 $default_permissions['allow_cid'] = $allowedCids;
159                         }
160
161                         $x = [
162                                 'lockstate' => self::$groupId || self::$forumContactId || self::$network || ACL::getLockstateForUserId($a->getLoggedInUserId()) ? 'lock' : 'unlock',
163                                 'acl' => ACL::getFullSelectorHTML(DI::page(), $a->getLoggedInUserId(), true, $default_permissions),
164                                 'bang' => ((self::$groupId || self::$forumContactId || self::$network) ? '!' : ''),
165                                 'content' => $content,
166                         ];
167
168                         $o .= DI::conversation()->statusEditor($x);
169                 }
170
171                 if (self::$groupId) {
172                         $group = DBA::selectFirst('group', ['name'], ['id' => self::$groupId, 'uid' => DI::userSession()->getLocalUserId()]);
173                         if (!DBA::isResult($group)) {
174                                 DI::sysmsg()->addNotice(DI::l10n()->t('No such group'));
175                         }
176
177                         $o = Renderer::replaceMacros(Renderer::getMarkupTemplate('section_title.tpl'), [
178                                 '$title' => DI::l10n()->t('Group: %s', $group['name'])
179                         ]) . $o;
180                 } elseif (self::$forumContactId) {
181                         $contact = Contact::getById(self::$forumContactId);
182                         if (DBA::isResult($contact)) {
183                                 $o = Renderer::replaceMacros(Renderer::getMarkupTemplate('contact/list.tpl'), [
184                                         'contacts' => [ModuleContact::getContactTemplateVars($contact)],
185                                         'id' => DI::args()->get(0),
186                                 ]) . $o;
187                         } else {
188                                 DI::sysmsg()->addNotice(DI::l10n()->t('Invalid contact.'));
189                         }
190                 } elseif (!DI::config()->get('theme', 'hide_eventlist')) {
191                         $o .= Profile::getBirthdays();
192                         $o .= Profile::getEventsReminderHTML();
193                 }
194
195                 if (self::$order === 'received') {
196                         $ordering = '`received`';
197                 } elseif (self::$order === 'created') {
198                         $ordering = '`created`';
199                 } else {
200                         $ordering = '`commented`';
201                 }
202
203                 $o .= DI::conversation()->create($items, 'network', false, false, $ordering, DI::userSession()->getLocalUserId());
204
205                 if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'infinite_scroll')) {
206                         $o .= HTML::scrollLoader();
207                 } else {
208                         $pager = new BoundariesPager(
209                                 DI::l10n(),
210                                 DI::args()->getQueryString(),
211                                 $items[0][self::$order] ?? null,
212                                 $items[count($items) - 1][self::$order] ?? null,
213                                 self::$itemsPerPage
214                         );
215
216                         $o .= $pager->renderMinimal(count($items));
217                 }
218
219                 return $o;
220         }
221
222         /**
223          * Sets items as seen
224          *
225          * @param array $condition The array with the SQL condition
226          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
227          */
228         private static function setItemsSeenByCondition(array $condition)
229         {
230                 if (empty($condition)) {
231                         return;
232                 }
233
234                 $unseen = Post::exists($condition);
235
236                 if ($unseen) {
237                         /// @todo handle huge "unseen" updates in the background to avoid timeout errors
238                         Item::update(['unseen' => false], $condition);
239                 }
240         }
241
242         /**
243          * Get the network tabs menu
244          *
245          * @param string $selectedTab
246          * @return string Html of the network tabs
247          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
248          */
249         private static function getTabsHTML(string $selectedTab)
250         {
251                 $cmd = DI::args()->getCommand();
252
253                 // tabs
254                 $tabs = [
255                         [
256                                 'label' => DI::l10n()->t('Latest Activity'),
257                                 'url'   => $cmd . '?' . http_build_query(['order' => 'commented']),
258                                 'sel'   => !$selectedTab || $selectedTab == 'commented' ? 'active' : '',
259                                 'title' => DI::l10n()->t('Sort by latest activity'),
260                                 'id'    => 'activity-order-tab',
261                                 'accesskey' => 'e',
262                         ],
263                         [
264                                 'label' => DI::l10n()->t('Latest Posts'),
265                                 'url'   => $cmd . '?' . http_build_query(['order' => 'received']),
266                                 'sel'   => $selectedTab == 'received' ? 'active' : '',
267                                 'title' => DI::l10n()->t('Sort by post received date'),
268                                 'id'    => 'post-order-tab',
269                                 'accesskey' => 't',
270                         ],
271                         [
272                                 'label' => DI::l10n()->t('Latest Creation'),
273                                 'url'   => $cmd . '?' . http_build_query(['order' => 'created']),
274                                 'sel'   => $selectedTab == 'created' ? 'active' : '',
275                                 'title' => DI::l10n()->t('Sort by post creation date'),
276                                 'id'    => 'creation-order-tab',
277                                 'accesskey' => 'q',
278                         ],
279                         [
280                                 'label' => DI::l10n()->t('Personal'),
281                                 'url'   => $cmd . '?' . http_build_query(['mention' => true]),
282                                 'sel'   => $selectedTab == 'mention' ? 'active' : '',
283                                 'title' => DI::l10n()->t('Posts that mention or involve you'),
284                                 'id'    => 'personal-tab',
285                                 'accesskey' => 'r',
286                         ],
287                         [
288                                 'label' => DI::l10n()->t('Starred'),
289                                 'url'   => $cmd . '?' . http_build_query(['star' => true]),
290                                 'sel'   => $selectedTab == 'star' ? 'active' : '',
291                                 'title' => DI::l10n()->t('Favourite Posts'),
292                                 'id'    => 'starred-posts-tab',
293                                 'accesskey' => 'm',
294                         ],
295                 ];
296
297                 $arr = ['tabs' => $tabs];
298                 Hook::callAll('network_tabs', $arr);
299
300                 $tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
301
302                 return Renderer::replaceMacros($tpl, ['$tabs' => $arr['tabs']]);
303         }
304
305         protected function parseRequest(array $get)
306         {
307                 self::$groupId = $this->parameters['group_id'] ?? 0;
308
309                 self::$forumContactId = $this->parameters['contact_id'] ?? 0;
310
311                 self::$selectedTab = self::getTimelineOrderBySession(DI::userSession(), DI::pConfig());
312
313                 if (!empty($get['star'])) {
314                         self::$selectedTab = 'star';
315                         self::$star = true;
316                 } else {
317                         self::$star = self::$selectedTab == 'star';
318                 }
319
320                 if (!empty($get['mention'])) {
321                         self::$selectedTab = 'mention';
322                         self::$mention = true;
323                 } else {
324                         self::$mention = self::$selectedTab == 'mention';
325                 }
326
327                 if (!empty($get['order'])) {
328                         self::$selectedTab = $get['order'];
329                         self::$order = $get['order'];
330                         self::$star = false;
331                         self::$mention = false;
332                 } elseif (in_array(self::$selectedTab, ['received', 'star'])) {
333                         self::$order = 'received';
334                 } elseif (self::$selectedTab == 'created') {
335                         self::$order = 'created';
336                 } else {
337                         self::$order = 'commented';
338                 }
339
340                 self::$selectedTab = self::$selectedTab ?? self::$order;
341
342                 // Prohibit combined usage of "star" and "mention"
343                 if (self::$selectedTab == 'star') {
344                         self::$mention = false;
345                 } elseif (self::$selectedTab == 'mention') {
346                         self::$star = false;
347                 }
348
349                 DI::session()->set('network-tab', self::$selectedTab);
350                 DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'network.view', 'selected_tab', self::$selectedTab);
351
352                 self::$accountTypeString = $get['accounttype'] ?? $this->parameters['accounttype'] ?? '';
353                 self::$accountType = User::getAccountTypeByString(self::$accountTypeString);
354
355                 self::$network = $get['nets'] ?? '';
356
357                 self::$dateFrom = $this->parameters['from'] ?? '';
358                 self::$dateTo = $this->parameters['to'] ?? '';
359
360                 if (DI::mode()->isMobile()) {
361                         self::$itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_mobile_network',
362                                 DI::config()->get('system', 'itemspage_network_mobile'));
363                 } else {
364                         self::$itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_network',
365                                 DI::config()->get('system', 'itemspage_network'));
366                 }
367
368                 self::$min_id = $get['min_id'] ?? null;
369                 self::$max_id = $get['max_id'] ?? null;
370
371                 switch (self::$order) {
372                         case 'received':
373                                 self::$max_id = $get['last_received'] ?? self::$max_id;
374                                 break;
375                         case 'created':
376                                 self::$max_id = $get['last_created'] ?? self::$max_id;
377                                 break;
378                         case 'uriid':
379                                 self::$max_id = $get['last_uriid'] ?? self::$max_id;
380                                 break;
381                         default:
382                                 self::$order = 'commented';
383                                 self::$max_id = $get['last_commented'] ?? self::$max_id;
384                 }
385         }
386
387         protected static function getItems(string $table, array $params, array $conditionFields = [])
388         {
389                 $conditionFields['uid'] = DI::userSession()->getLocalUserId();
390                 $conditionStrings = [];
391
392                 if (!is_null(self::$accountType)) {
393                         $conditionFields['contact-type'] = self::$accountType;
394                 }
395
396                 if (self::$star) {
397                         $conditionFields['starred'] = true;
398                 }
399                 if (self::$mention) {
400                         $conditionFields['mention'] = true;
401                 }
402                 if (self::$network) {
403                         $conditionFields['network'] = self::$network;
404                 }
405
406                 if (self::$dateFrom) {
407                         $conditionStrings = DBA::mergeConditions($conditionStrings, ["`received` <= ? ", DateTimeFormat::convert(self::$dateFrom, 'UTC', DI::app()->getTimeZone())]);
408                 }
409                 if (self::$dateTo) {
410                         $conditionStrings = DBA::mergeConditions($conditionStrings, ["`received` >= ? ", DateTimeFormat::convert(self::$dateTo, 'UTC', DI::app()->getTimeZone())]);
411                 }
412
413                 if (self::$groupId) {
414                         $conditionStrings = DBA::mergeConditions($conditionStrings, ["`contact-id` IN (SELECT `contact-id` FROM `group_member` WHERE `gid` = ?)", self::$groupId]);
415                 } elseif (self::$forumContactId) {
416                         $conditionStrings = DBA::mergeConditions($conditionStrings, 
417                                 ["((`contact-id` = ?) OR `uri-id` IN (SELECT `parent-uri-id` FROM `post-user-view` WHERE (`contact-id` = ? AND `gravity` = ? AND `vid` = ? AND `uid` = ?)))",
418                                 self::$forumContactId, self::$forumContactId, Item::GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE), DI::userSession()->getLocalUserId()]);
419                 }
420
421                 // Currently only the order modes "received" and "commented" are in use
422                 if (isset(self::$max_id)) {
423                         switch (self::$order) {
424                                 case 'received':
425                                         $conditionStrings = DBA::mergeConditions($conditionStrings, ["`received` < ?", self::$max_id]);
426                                         break;
427                                 case 'commented':
428                                         $conditionStrings = DBA::mergeConditions($conditionStrings, ["`commented` < ?", self::$max_id]);
429                                         break;
430                                 case 'created':
431                                         $conditionStrings = DBA::mergeConditions($conditionStrings, ["`created` < ?", self::$max_id]);
432                                         break;
433                                 case 'uriid':
434                                         $conditionStrings = DBA::mergeConditions($conditionStrings, ["`uri-id` < ?", self::$max_id]);
435                                         break;
436                         }
437                 }
438
439                 if (isset(self::$min_id)) {
440                         switch (self::$order) {
441                                 case 'received':
442                                         $conditionStrings = DBA::mergeConditions($conditionStrings, ["`received` > ?", self::$min_id]);
443                                         break;
444                                 case 'commented':
445                                         $conditionStrings = DBA::mergeConditions($conditionStrings, ["`commented` > ?", self::$min_id]);
446                                         break;
447                                 case 'created':
448                                         $conditionStrings = DBA::mergeConditions($conditionStrings, ["`created` > ?", self::$min_id]);
449                                         break;
450                                 case 'uriid':
451                                         $conditionStrings = DBA::mergeConditions($conditionStrings, ["`uri-id` > ?", self::$min_id]);
452                                         break;
453                         }
454                 }
455
456                 if (isset(self::$min_id) && !isset(self::$max_id)) {
457                         // min_id quirk: querying in reverse order with min_id gets the most recent rows, regardless of how close
458                         // they are to min_id. We change the query ordering to get the expected data, and we need to reverse the
459                         // order of the results.
460                         $params['order'] = [self::$order => false];
461                 } else {
462                         $params['order'] = [self::$order => true];
463                 }
464
465                 $items = DBA::selectToArray($table, [], DBA::mergeConditions($conditionFields, $conditionStrings), $params);
466
467                 // min_id quirk, continued
468                 if (isset(self::$min_id) && !isset(self::$max_id)) {
469                         $items = array_reverse($items);
470                 }
471
472                 if (DBA::isResult($items)) {
473                         $parents = array_column($items, 'parent-uri-id');
474                 } else {
475                         $parents = [];
476                 }
477
478                 // We aren't going to try and figure out at the item, group, and page
479                 // level which items you've seen and which you haven't. If you're looking
480                 // at the top level network page just mark everything seen.
481                 if (!self::$groupId && !self::$forumContactId && !self::$star && !self::$mention) {
482                         $condition = ['unseen' => true, 'uid' => DI::userSession()->getLocalUserId()];
483                         self::setItemsSeenByCondition($condition);
484                 } elseif (!empty($parents)) {
485                         $condition = ['unseen' => true, 'uid' => DI::userSession()->getLocalUserId(), 'parent-uri-id' => $parents];
486                         self::setItemsSeenByCondition($condition);
487                 }
488
489                 return $items;
490         }
491
492         /**
493          * Returns the selected network tab of the currently logged-in user
494          *
495          * @param IHandleUserSessions         $session
496          * @param IManagePersonalConfigValues $pconfig
497          * @return string
498          */
499         public static function getTimelineOrderBySession(IHandleUserSessions $session, IManagePersonalConfigValues $pconfig): string
500         {
501                 return $session->get('network-tab')
502                         ?? $pconfig->get($session->getLocalUserId(), 'network.view', 'selected_tab')
503                         ?? '';
504         }
505 }