]> git.mxchange.org Git - friendica.git/blob - src/Model/Profile.php
Log function
[friendica.git] / src / Model / Profile.php
1 <?php
2 /**
3  * @file src/Model/Profile.php
4  */
5 namespace Friendica\Model;
6
7 use Friendica\App;
8 use Friendica\Content\Feature;
9 use Friendica\Content\ForumManager;
10 use Friendica\Content\Text\BBCode;
11 use Friendica\Core\Addon;
12 use Friendica\Core\Cache;
13 use Friendica\Core\Config;
14 use Friendica\Core\L10n;
15 use Friendica\Core\Logger;
16 use Friendica\Core\PConfig;
17 use Friendica\Core\Protocol;
18 use Friendica\Core\System;
19 use Friendica\Core\Worker;
20 use Friendica\Database\DBA;
21 use Friendica\Model\Contact;
22 use Friendica\Protocol\Diaspora;
23 use Friendica\Util\DateTimeFormat;
24 use Friendica\Util\Network;
25 use Friendica\Util\Proxy as ProxyUtils;
26 use Friendica\Util\Temporal;
27
28 require_once 'include/dba.php';
29
30 class Profile
31 {
32         /**
33          * @brief Returns default profile for a given user id
34          *
35          * @param integer User ID
36          *
37          * @return array Profile data
38          */
39         public static function getByUID($uid)
40         {
41                 $profile = DBA::selectFirst('profile', [], ['uid' => $uid, 'is-default' => true]);
42                 return $profile;
43         }
44
45         /**
46          * @brief Returns a formatted location string from the given profile array
47          *
48          * @param array $profile Profile array (Generated from the "profile" table)
49          *
50          * @return string Location string
51          */
52         public static function formatLocation(array $profile)
53         {
54                 $location = '';
55
56                 if (!empty($profile['locality'])) {
57                         $location .= $profile['locality'];
58                 }
59
60                 if (!empty($profile['region']) && (defaults($profile, 'locality', '') != $profile['region'])) {
61                         if ($location) {
62                                 $location .= ', ';
63                         }
64
65                         $location .= $profile['region'];
66                 }
67
68                 if (!empty($profile['country-name'])) {
69                         if ($location) {
70                                 $location .= ', ';
71                         }
72
73                         $location .= $profile['country-name'];
74                 }
75
76                 return $location;
77         }
78
79         /**
80          *
81          * Loads a profile into the page sidebar.
82          *
83          * The function requires a writeable copy of the main App structure, and the nickname
84          * of a registered local account.
85          *
86          * If the viewer is an authenticated remote viewer, the profile displayed is the
87          * one that has been configured for his/her viewing in the Contact manager.
88          * Passing a non-zero profile ID can also allow a preview of a selected profile
89          * by the owner.
90          *
91          * Profile information is placed in the App structure for later retrieval.
92          * Honours the owner's chosen theme for display.
93          *
94          * @attention Should only be run in the _init() functions of a module. That ensures that
95          *      the theme is chosen before the _init() function of a theme is run, which will usually
96          *      load a lot of theme-specific content
97          *
98          * @brief Loads a profile into the page sidebar.
99          * @param object  $a            App
100          * @param string  $nickname     string
101          * @param int     $profile      int
102          * @param array   $profiledata  array
103          * @param boolean $show_connect Show connect link
104          */
105         public static function load(App $a, $nickname, $profile = 0, array $profiledata = [], $show_connect = true)
106         {
107                 $user = DBA::selectFirst('user', ['uid'], ['nickname' => $nickname, 'account_removed' => false]);
108
109                 if (!DBA::isResult($user) && empty($profiledata)) {
110                         Logger::log('profile error: ' . $a->query_string, LOGGER_DEBUG);
111                         notice(L10n::t('Requested account is not available.') . EOL);
112                         $a->error = 404;
113                         return;
114                 }
115
116                 if (count($profiledata) > 0) {
117                         // Add profile data to sidebar
118                         $a->page['aside'] .= self::sidebar($profiledata, true, $show_connect);
119
120                         if (!DBA::isResult($user)) {
121                                 return;
122                         }
123                 }
124
125                 $pdata = self::getByNickname($nickname, $user['uid'], $profile);
126
127                 if (empty($pdata) && empty($profiledata)) {
128                         Logger::log('profile error: ' . $a->query_string, LOGGER_DEBUG);
129                         notice(L10n::t('Requested profile is not available.') . EOL);
130                         $a->error = 404;
131                         return;
132                 }
133
134                 if (empty($pdata)) {
135                         $pdata = ['uid' => 0, 'profile_uid' => 0, 'is-default' => false,'name' => $nickname];
136                 }
137
138                 // fetch user tags if this isn't the default profile
139
140                 if (!$pdata['is-default']) {
141                         $condition = ['uid' => $pdata['profile_uid'], 'is-default' => true];
142                         $profile = DBA::selectFirst('profile', ['pub_keywords'], $condition);
143                         if (DBA::isResult($profile)) {
144                                 $pdata['pub_keywords'] = $profile['pub_keywords'];
145                         }
146                 }
147
148                 $a->profile = $pdata;
149                 $a->profile_uid = $pdata['profile_uid'];
150
151                 $a->profile['mobile-theme'] = PConfig::get($a->profile['profile_uid'], 'system', 'mobile_theme');
152                 $a->profile['network'] = Protocol::DFRN;
153
154                 $a->page['title'] = $a->profile['name'] . ' @ ' . Config::get('config', 'sitename');
155
156                 if (!$profiledata && !PConfig::get(local_user(), 'system', 'always_my_theme')) {
157                         $_SESSION['theme'] = $a->profile['theme'];
158                 }
159
160                 $_SESSION['mobile-theme'] = $a->profile['mobile-theme'];
161
162                 /*
163                 * load/reload current theme info
164                 */
165
166                 $a->setActiveTemplateEngine(); // reset the template engine to the default in case the user's theme doesn't specify one
167
168                 $theme_info_file = 'view/theme/' . $a->getCurrentTheme() . '/theme.php';
169                 if (file_exists($theme_info_file)) {
170                         require_once $theme_info_file;
171                 }
172
173                 if (local_user() && local_user() == $a->profile['uid'] && $profiledata) {
174                         $a->page['aside'] .= replace_macros(
175                                 get_markup_template('profile_edlink.tpl'),
176                                 [
177                                         '$editprofile' => L10n::t('Edit profile'),
178                                         '$profid' => $a->profile['id']
179                                 ]
180                         );
181                 }
182
183                 $block = ((Config::get('system', 'block_public') && !local_user() && !remote_user()) ? true : false);
184
185                 /**
186                  * @todo
187                  * By now, the contact block isn't shown, when a different profile is given
188                  * But: When this profile was on the same server, then we could display the contacts
189                  */
190                 if (!$profiledata) {
191                         $a->page['aside'] .= self::sidebar($a->profile, $block, $show_connect);
192                 }
193
194                 return;
195         }
196
197         /**
198          * Get all profile data of a local user
199          *
200          * If the viewer is an authenticated remote viewer, the profile displayed is the
201          * one that has been configured for his/her viewing in the Contact manager.
202          * Passing a non-zero profile ID can also allow a preview of a selected profile
203          * by the owner
204          *
205          * Includes all available profile data
206          *
207          * @brief Get all profile data of a local user
208          * @param string $nickname nick
209          * @param int    $uid      uid
210          * @param int    $profile_id  ID of the profile
211          * @return array
212          */
213         public static function getByNickname($nickname, $uid = 0, $profile_id = 0)
214         {
215                 if (remote_user() && !empty($_SESSION['remote'])) {
216                         foreach ($_SESSION['remote'] as $visitor) {
217                                 if ($visitor['uid'] == $uid) {
218                                         $contact = DBA::selectFirst('contact', ['profile-id'], ['id' => $visitor['cid']]);
219                                         if (DBA::isResult($contact)) {
220                                                 $profile_id = $contact['profile-id'];
221                                         }
222                                         break;
223                                 }
224                         }
225                 }
226
227                 $profile = null;
228
229                 if ($profile_id) {
230                         $profile = DBA::fetchFirst(
231                                 "SELECT `contact`.`id` AS `contact_id`, `contact`.`photo` AS `contact_photo`,
232                                         `contact`.`thumb` AS `contact_thumb`, `contact`.`micro` AS `contact_micro`,
233                                         `profile`.`uid` AS `profile_uid`, `profile`.*,
234                                         `contact`.`avatar-date` AS picdate, `contact`.`addr`, `contact`.`url`, `user`.*
235                                 FROM `profile`
236                                 INNER JOIN `contact` on `contact`.`uid` = `profile`.`uid` AND `contact`.`self`
237                                 INNER JOIN `user` ON `profile`.`uid` = `user`.`uid`
238                                 WHERE `user`.`nickname` = ? AND `profile`.`id` = ? LIMIT 1",
239                                 $nickname,
240                                 intval($profile_id)
241                         );
242                 }
243                 if (!DBA::isResult($profile)) {
244                         $profile = DBA::fetchFirst(
245                                 "SELECT `contact`.`id` AS `contact_id`, `contact`.`photo` as `contact_photo`,
246                                         `contact`.`thumb` AS `contact_thumb`, `contact`.`micro` AS `contact_micro`,
247                                         `profile`.`uid` AS `profile_uid`, `profile`.*,
248                                         `contact`.`avatar-date` AS picdate, `contact`.`addr`, `contact`.`url`, `user`.*
249                                 FROM `profile`
250                                 INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid` AND `contact`.`self`
251                                 INNER JOIN `user` ON `profile`.`uid` = `user`.`uid`
252                                 WHERE `user`.`nickname` = ? AND `profile`.`is-default` LIMIT 1",
253                                 $nickname
254                         );
255                 }
256
257                 return $profile;
258         }
259
260         /**
261          * Formats a profile for display in the sidebar.
262          *
263          * It is very difficult to templatise the HTML completely
264          * because of all the conditional logic.
265          *
266          * @brief Formats a profile for display in the sidebar.
267          * @param array $profile
268          * @param int $block
269          * @param boolean $show_connect Show connect link
270          *
271          * @return string HTML sidebar module
272          *
273          * @note Returns empty string if passed $profile is wrong type or not populated
274          *
275          * @hooks 'profile_sidebar_enter'
276          *      array $profile - profile data
277          * @hooks 'profile_sidebar'
278          *      array $arr
279          */
280         private static function sidebar($profile, $block = 0, $show_connect = true)
281         {
282                 $a = get_app();
283
284                 $o = '';
285                 $location = false;
286
287                 // This function can also use contact information in $profile
288                 $is_contact = x($profile, 'cid');
289
290                 if (!is_array($profile) && !count($profile)) {
291                         return $o;
292                 }
293
294                 $profile['picdate'] = urlencode(defaults($profile, 'picdate', ''));
295
296                 if (($profile['network'] != '') && ($profile['network'] != Protocol::DFRN)) {
297                         $profile['network_name'] = format_network_name($profile['network'], $profile['url']);
298                 } else {
299                         $profile['network_name'] = '';
300                 }
301
302                 Addon::callHooks('profile_sidebar_enter', $profile);
303
304
305                 // don't show connect link to yourself
306                 $connect = $profile['uid'] != local_user() ? L10n::t('Connect') : false;
307
308                 // don't show connect link to authenticated visitors either
309                 if (remote_user() && !empty($_SESSION['remote'])) {
310                         foreach ($_SESSION['remote'] as $visitor) {
311                                 if ($visitor['uid'] == $profile['uid']) {
312                                         $connect = false;
313                                         break;
314                                 }
315                         }
316                 }
317
318                 if (!$show_connect) {
319                         $connect = false;
320                 }
321
322                 $profile_url = '';
323
324                 // Is the local user already connected to that user?
325                 if ($connect && local_user()) {
326                         if (isset($profile['url'])) {
327                                 $profile_url = normalise_link($profile['url']);
328                         } else {
329                                 $profile_url = normalise_link(System::baseUrl() . '/profile/' . $profile['nickname']);
330                         }
331
332                         if (DBA::exists('contact', ['pending' => false, 'uid' => local_user(), 'nurl' => $profile_url])) {
333                                 $connect = false;
334                         }
335                 }
336
337                 if ($connect && ($profile['network'] != Protocol::DFRN) && !isset($profile['remoteconnect'])) {
338                         $connect = false;
339                 }
340
341                 $remoteconnect = null;
342                 if (isset($profile['remoteconnect'])) {
343                         $remoteconnect = $profile['remoteconnect'];
344                 }
345
346                 if ($connect && ($profile['network'] == Protocol::DFRN) && !isset($remoteconnect)) {
347                         $subscribe_feed = L10n::t('Atom feed');
348                 } else {
349                         $subscribe_feed = false;
350                 }
351
352                 $wallmessage = false;
353                 $wallmessage_link = false;
354
355                 // See issue https://github.com/friendica/friendica/issues/3838
356                 // Either we remove the message link for remote users or we enable creating messages from remote users
357                 if (remote_user() || (self::getMyURL() && x($profile, 'unkmail') && ($profile['uid'] != local_user()))) {
358                         $wallmessage = L10n::t('Message');
359
360                         if (remote_user()) {
361                                 $r = q(
362                                         "SELECT `url` FROM `contact` WHERE `uid` = %d AND `id` = '%s' AND `rel` = %d",
363                                         intval($profile['uid']),
364                                         intval(remote_user()),
365                                         intval(Contact::FRIEND)
366                                 );
367                         } else {
368                                 $r = q(
369                                         "SELECT `url` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `rel` = %d",
370                                         intval($profile['uid']),
371                                         DBA::escape(normalise_link(self::getMyURL())),
372                                         intval(Contact::FRIEND)
373                                 );
374                         }
375                         if ($r) {
376                                 $remote_url = $r[0]['url'];
377                                 $message_path = preg_replace('=(.*)/profile/(.*)=ism', '$1/message/new/', $remote_url);
378                                 $wallmessage_link = $message_path . base64_encode(defaults($profile, 'addr', ''));
379                         } else if (!empty($profile['nickname'])) {
380                                 $wallmessage_link = 'wallmessage/' . $profile['nickname'];
381                         }
382                 }
383
384                 // show edit profile to yourself
385                 if (!$is_contact && $profile['uid'] == local_user() && Feature::isEnabled(local_user(), 'multi_profiles')) {
386                         $profile['edit'] = [System::baseUrl() . '/profiles', L10n::t('Profiles'), '', L10n::t('Manage/edit profiles')];
387                         $r = q(
388                                 "SELECT * FROM `profile` WHERE `uid` = %d",
389                                 local_user()
390                         );
391
392                         $profile['menu'] = [
393                                 'chg_photo' => L10n::t('Change profile photo'),
394                                 'cr_new' => L10n::t('Create New Profile'),
395                                 'entries' => [],
396                         ];
397
398                         if (DBA::isResult($r)) {
399                                 foreach ($r as $rr) {
400                                         $profile['menu']['entries'][] = [
401                                                 'photo' => $rr['thumb'],
402                                                 'id' => $rr['id'],
403                                                 'alt' => L10n::t('Profile Image'),
404                                                 'profile_name' => $rr['profile-name'],
405                                                 'isdefault' => $rr['is-default'],
406                                                 'visibile_to_everybody' => L10n::t('visible to everybody'),
407                                                 'edit_visibility' => L10n::t('Edit visibility'),
408                                         ];
409                                 }
410                         }
411                 }
412                 if (!$is_contact && $profile['uid'] == local_user() && !Feature::isEnabled(local_user(), 'multi_profiles')) {
413                         $profile['edit'] = [System::baseUrl() . '/profiles/' . $profile['id'], L10n::t('Edit profile'), '', L10n::t('Edit profile')];
414                         $profile['menu'] = [
415                                 'chg_photo' => L10n::t('Change profile photo'),
416                                 'cr_new' => null,
417                                 'entries' => [],
418                         ];
419                 }
420
421                 // Fetch the account type
422                 $account_type = Contact::getAccountType($profile);
423
424                 if (x($profile, 'address')
425                         || x($profile, 'location')
426                         || x($profile, 'locality')
427                         || x($profile, 'region')
428                         || x($profile, 'postal-code')
429                         || x($profile, 'country-name')
430                 ) {
431                         $location = L10n::t('Location:');
432                 }
433
434                 $gender   = x($profile, 'gender')   ? L10n::t('Gender:')   : false;
435                 $marital  = x($profile, 'marital')  ? L10n::t('Status:')   : false;
436                 $homepage = x($profile, 'homepage') ? L10n::t('Homepage:') : false;
437                 $about    = x($profile, 'about')    ? L10n::t('About:')    : false;
438                 $xmpp     = x($profile, 'xmpp')     ? L10n::t('XMPP:')     : false;
439
440                 if ((x($profile, 'hidewall') || $block) && !local_user() && !remote_user()) {
441                         $location = $gender = $marital = $homepage = $about = false;
442                 }
443
444                 $split_name = Diaspora::splitName($profile['name']);
445                 $firstname = $split_name['first'];
446                 $lastname = $split_name['last'];
447
448                 if (x($profile, 'guid')) {
449                         $diaspora = [
450                                 'guid' => $profile['guid'],
451                                 'podloc' => System::baseUrl(),
452                                 'searchable' => (($profile['publish'] && $profile['net-publish']) ? 'true' : 'false' ),
453                                 'nickname' => $profile['nickname'],
454                                 'fullname' => $profile['name'],
455                                 'firstname' => $firstname,
456                                 'lastname' => $lastname,
457                                 'photo300' => defaults($profile, 'contact_photo', ''),
458                                 'photo100' => defaults($profile, 'contact_thumb', ''),
459                                 'photo50' => defaults($profile, 'contact_micro', ''),
460                         ];
461                 } else {
462                         $diaspora = false;
463                 }
464
465                 $contact_block = '';
466                 $updated = '';
467                 $contacts = 0;
468                 if (!$block) {
469                         $contact_block = contact_block();
470
471                         if (is_array($a->profile) && !$a->profile['hide-friends']) {
472                                 $r = q(
473                                         "SELECT `gcontact`.`updated` FROM `contact` INNER JOIN `gcontact` WHERE `gcontact`.`nurl` = `contact`.`nurl` AND `self` AND `uid` = %d LIMIT 1",
474                                         intval($a->profile['uid'])
475                                 );
476                                 if (DBA::isResult($r)) {
477                                         $updated = date('c', strtotime($r[0]['updated']));
478                                 }
479
480                                 $r = q(
481                                         "SELECT COUNT(*) AS `total` FROM `contact`
482                                         WHERE `uid` = %d
483                                                 AND NOT `self` AND NOT `blocked` AND NOT `pending`
484                                                 AND NOT `hidden` AND NOT `archive`
485                                                 AND `network` IN ('%s', '%s', '%s', '')",
486                                         intval($profile['uid']),
487                                         DBA::escape(Protocol::DFRN),
488                                         DBA::escape(Protocol::DIASPORA),
489                                         DBA::escape(Protocol::OSTATUS)
490                                 );
491                                 if (DBA::isResult($r)) {
492                                         $contacts = intval($r[0]['total']);
493                                 }
494                         }
495                 }
496
497                 $p = [];
498                 foreach ($profile as $k => $v) {
499                         $k = str_replace('-', '_', $k);
500                         $p[$k] = $v;
501                 }
502
503                 if (isset($p['about'])) {
504                         $p['about'] = BBCode::convert($p['about']);
505                 }
506
507                 if (isset($p['address'])) {
508                         $p['address'] = BBCode::convert($p['address']);
509                 } elseif (isset($p['location'])) {
510                         $p['address'] = BBCode::convert($p['location']);
511                 }
512
513                 if (isset($p['photo'])) {
514                         $p['photo'] = ProxyUtils::proxifyUrl($p['photo'], false, ProxyUtils::SIZE_SMALL);
515                 }
516
517                 $p['url'] = Contact::magicLink(defaults($p, 'url', $profile_url));
518
519                 $tpl = get_markup_template('profile_vcard.tpl');
520                 $o .= replace_macros($tpl, [
521                         '$profile' => $p,
522                         '$xmpp' => $xmpp,
523                         '$connect' => $connect,
524                         '$remoteconnect' => $remoteconnect,
525                         '$subscribe_feed' => $subscribe_feed,
526                         '$wallmessage' => $wallmessage,
527                         '$wallmessage_link' => $wallmessage_link,
528                         '$account_type' => $account_type,
529                         '$location' => $location,
530                         '$gender' => $gender,
531                         '$marital' => $marital,
532                         '$homepage' => $homepage,
533                         '$about' => $about,
534                         '$network' => L10n::t('Network:'),
535                         '$contacts' => $contacts,
536                         '$updated' => $updated,
537                         '$diaspora' => $diaspora,
538                         '$contact_block' => $contact_block,
539                 ]);
540
541                 $arr = ['profile' => &$profile, 'entry' => &$o];
542
543                 Addon::callHooks('profile_sidebar', $arr);
544
545                 return $o;
546         }
547
548         public static function getBirthdays()
549         {
550                 $a = get_app();
551                 $o = '';
552
553                 if (!local_user() || $a->is_mobile || $a->is_tablet) {
554                         return $o;
555                 }
556
557                 /*
558                 * $mobile_detect = new Mobile_Detect();
559                 * $is_mobile = $mobile_detect->isMobile() || $mobile_detect->isTablet();
560                 *               if ($is_mobile)
561                 *                       return $o;
562                 */
563
564                 $bd_format = L10n::t('g A l F d'); // 8 AM Friday January 18
565                 $bd_short = L10n::t('F d');
566
567                 $cachekey = 'get_birthdays:' . local_user();
568                 $r = Cache::get($cachekey);
569                 if (is_null($r)) {
570                         $s = DBA::p(
571                                 "SELECT `event`.*, `event`.`id` AS `eid`, `contact`.* FROM `event`
572                                 INNER JOIN `contact` ON `contact`.`id` = `event`.`cid`
573                                 WHERE `event`.`uid` = ? AND `type` = 'birthday' AND `start` < ? AND `finish` > ?
574                                 ORDER BY `start` ASC ",
575                                 local_user(),
576                                 DateTimeFormat::utc('now + 6 days'),
577                                 DateTimeFormat::utcNow()
578                         );
579                         if (DBA::isResult($s)) {
580                                 $r = DBA::toArray($s);
581                                 Cache::set($cachekey, $r, Cache::HOUR);
582                         }
583                 }
584
585                 $total = 0;
586                 $classtoday = '';
587                 if (DBA::isResult($r)) {
588                         $now = strtotime('now');
589                         $cids = [];
590
591                         $istoday = false;
592                         foreach ($r as $rr) {
593                                 if (strlen($rr['name'])) {
594                                         $total ++;
595                                 }
596                                 if ((strtotime($rr['start'] . ' +00:00') < $now) && (strtotime($rr['finish'] . ' +00:00') > $now)) {
597                                         $istoday = true;
598                                 }
599                         }
600                         $classtoday = $istoday ? ' birthday-today ' : '';
601                         if ($total) {
602                                 foreach ($r as &$rr) {
603                                         if (!strlen($rr['name'])) {
604                                                 continue;
605                                         }
606
607                                         // avoid duplicates
608
609                                         if (in_array($rr['cid'], $cids)) {
610                                                 continue;
611                                         }
612                                         $cids[] = $rr['cid'];
613
614                                         $today = (((strtotime($rr['start'] . ' +00:00') < $now) && (strtotime($rr['finish'] . ' +00:00') > $now)) ? true : false);
615
616                                         $rr['link'] = Contact::magicLink($rr['url']);
617                                         $rr['title'] = $rr['name'];
618                                         $rr['date'] = day_translate(DateTimeFormat::convert($rr['start'], $a->timezone, 'UTC', $rr['adjust'] ? $bd_format : $bd_short)) . (($today) ? ' ' . L10n::t('[today]') : '');
619                                         $rr['startime'] = null;
620                                         $rr['today'] = $today;
621                                 }
622                         }
623                 }
624                 $tpl = get_markup_template('birthdays_reminder.tpl');
625                 return replace_macros($tpl, [
626                         '$baseurl' => System::baseUrl(),
627                         '$classtoday' => $classtoday,
628                         '$count' => $total,
629                         '$event_reminders' => L10n::t('Birthday Reminders'),
630                         '$event_title' => L10n::t('Birthdays this week:'),
631                         '$events' => $r,
632                         '$lbr' => '{', // raw brackets mess up if/endif macro processing
633                         '$rbr' => '}'
634                 ]);
635         }
636
637         public static function getEventsReminderHTML()
638         {
639                 $a = get_app();
640                 $o = '';
641
642                 if (!local_user() || $a->is_mobile || $a->is_tablet) {
643                         return $o;
644                 }
645
646                 /*
647                 *       $mobile_detect = new Mobile_Detect();
648                 *               $is_mobile = $mobile_detect->isMobile() || $mobile_detect->isTablet();
649                 *               if ($is_mobile)
650                 *                       return $o;
651                 */
652
653                 $bd_format = L10n::t('g A l F d'); // 8 AM Friday January 18
654                 $classtoday = '';
655
656                 $condition = ["`uid` = ? AND `type` != 'birthday' AND `start` < ? AND `start` >= ?",
657                         local_user(), DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utc('now - 1 days')];
658                 $s = DBA::select('event', [], $condition, ['order' => ['start']]);
659
660                 $r = [];
661
662                 if (DBA::isResult($s)) {
663                         $istoday = false;
664                         $total = 0;
665
666                         while ($rr = DBA::fetch($s)) {
667                                 $condition = ['parent-uri' => $rr['uri'], 'uid' => $rr['uid'], 'author-id' => public_contact(),
668                                         'activity' => [Item::activityToIndex(ACTIVITY_ATTEND), Item::activityToIndex(ACTIVITY_ATTENDMAYBE)],
669                                         'visible' => true, 'deleted' => false];
670                                 if (!Item::exists($condition)) {
671                                         continue;
672                                 }
673
674                                 if (strlen($rr['summary'])) {
675                                         $total++;
676                                 }
677
678                                 $strt = DateTimeFormat::convert($rr['start'], $rr['adjust'] ? $a->timezone : 'UTC', 'UTC', 'Y-m-d');
679                                 if ($strt === DateTimeFormat::timezoneNow($a->timezone, 'Y-m-d')) {
680                                         $istoday = true;
681                                 }
682
683                                 $title = strip_tags(html_entity_decode(BBCode::convert($rr['summary']), ENT_QUOTES, 'UTF-8'));
684
685                                 if (strlen($title) > 35) {
686                                         $title = substr($title, 0, 32) . '... ';
687                                 }
688
689                                 $description = substr(strip_tags(BBCode::convert($rr['desc'])), 0, 32) . '... ';
690                                 if (!$description) {
691                                         $description = L10n::t('[No description]');
692                                 }
693
694                                 $strt = DateTimeFormat::convert($rr['start'], $rr['adjust'] ? $a->timezone : 'UTC');
695
696                                 if (substr($strt, 0, 10) < DateTimeFormat::timezoneNow($a->timezone, 'Y-m-d')) {
697                                         continue;
698                                 }
699
700                                 $today = ((substr($strt, 0, 10) === DateTimeFormat::timezoneNow($a->timezone, 'Y-m-d')) ? true : false);
701
702                                 $rr['title'] = $title;
703                                 $rr['description'] = $description;
704                                 $rr['date'] = day_translate(DateTimeFormat::convert($rr['start'], $rr['adjust'] ? $a->timezone : 'UTC', 'UTC', $bd_format)) . (($today) ? ' ' . L10n::t('[today]') : '');
705                                 $rr['startime'] = $strt;
706                                 $rr['today'] = $today;
707
708                                 $r[] = $rr;
709                         }
710                         DBA::close($s);
711                         $classtoday = (($istoday) ? 'event-today' : '');
712                 }
713                 $tpl = get_markup_template('events_reminder.tpl');
714                 return replace_macros($tpl, [
715                         '$baseurl' => System::baseUrl(),
716                         '$classtoday' => $classtoday,
717                         '$count' => count($r),
718                         '$event_reminders' => L10n::t('Event Reminders'),
719                         '$event_title' => L10n::t('Upcoming events the next 7 days:'),
720                         '$events' => $r,
721                 ]);
722         }
723
724         public static function getAdvanced(App $a)
725         {
726                 $o = '';
727                 $uid = $a->profile['uid'];
728
729                 $o .= replace_macros(
730                         get_markup_template('section_title.tpl'),
731                         ['$title' => L10n::t('Profile')]
732                 );
733
734                 if ($a->profile['name']) {
735                         $tpl = get_markup_template('profile_advanced.tpl');
736
737                         $profile = [];
738
739                         $profile['fullname'] = [L10n::t('Full Name:'), $a->profile['name']];
740
741                         if (Feature::isEnabled($uid, 'profile_membersince')) {
742                                 $profile['membersince'] = [L10n::t('Member since:'), DateTimeFormat::local($a->profile['register_date'])];
743                         }
744
745                         if ($a->profile['gender']) {
746                                 $profile['gender'] = [L10n::t('Gender:'), $a->profile['gender']];
747                         }
748
749                         if (($a->profile['dob']) && ($a->profile['dob'] > '0001-01-01')) {
750                                 $year_bd_format = L10n::t('j F, Y');
751                                 $short_bd_format = L10n::t('j F');
752
753                                 $val = day_translate(
754                                         intval($a->profile['dob']) ?
755                                                 DateTimeFormat::utc($a->profile['dob'] . ' 00:00 +00:00', $year_bd_format)
756                                                 : DateTimeFormat::utc('2001-' . substr($a->profile['dob'], 5) . ' 00:00 +00:00', $short_bd_format)
757                                 );
758
759                                 $profile['birthday'] = [L10n::t('Birthday:'), $val];
760                         }
761
762                         if (!empty($a->profile['dob'])
763                                 && $a->profile['dob'] > '0001-01-01'
764                                 && $age = Temporal::getAgeByTimezone($a->profile['dob'], $a->profile['timezone'], '')
765                         ) {
766                                 $profile['age'] = [L10n::t('Age:'), $age];
767                         }
768
769                         if ($a->profile['marital']) {
770                                 $profile['marital'] = [L10n::t('Status:'), $a->profile['marital']];
771                         }
772
773                         /// @TODO Maybe use x() here, plus below?
774                         if ($a->profile['with']) {
775                                 $profile['marital']['with'] = $a->profile['with'];
776                         }
777
778                         if (strlen($a->profile['howlong']) && $a->profile['howlong'] >= DBA::NULL_DATETIME) {
779                                 $profile['howlong'] = Temporal::getRelativeDate($a->profile['howlong'], L10n::t('for %1$d %2$s'));
780                         }
781
782                         if ($a->profile['sexual']) {
783                                 $profile['sexual'] = [L10n::t('Sexual Preference:'), $a->profile['sexual']];
784                         }
785
786                         if ($a->profile['homepage']) {
787                                 $profile['homepage'] = [L10n::t('Homepage:'), linkify($a->profile['homepage'])];
788                         }
789
790                         if ($a->profile['hometown']) {
791                                 $profile['hometown'] = [L10n::t('Hometown:'), linkify($a->profile['hometown'])];
792                         }
793
794                         if ($a->profile['pub_keywords']) {
795                                 $profile['pub_keywords'] = [L10n::t('Tags:'), $a->profile['pub_keywords']];
796                         }
797
798                         if ($a->profile['politic']) {
799                                 $profile['politic'] = [L10n::t('Political Views:'), $a->profile['politic']];
800                         }
801
802                         if ($a->profile['religion']) {
803                                 $profile['religion'] = [L10n::t('Religion:'), $a->profile['religion']];
804                         }
805
806                         if ($txt = prepare_text($a->profile['about'])) {
807                                 $profile['about'] = [L10n::t('About:'), $txt];
808                         }
809
810                         if ($txt = prepare_text($a->profile['interest'])) {
811                                 $profile['interest'] = [L10n::t('Hobbies/Interests:'), $txt];
812                         }
813
814                         if ($txt = prepare_text($a->profile['likes'])) {
815                                 $profile['likes'] = [L10n::t('Likes:'), $txt];
816                         }
817
818                         if ($txt = prepare_text($a->profile['dislikes'])) {
819                                 $profile['dislikes'] = [L10n::t('Dislikes:'), $txt];
820                         }
821
822                         if ($txt = prepare_text($a->profile['contact'])) {
823                                 $profile['contact'] = [L10n::t('Contact information and Social Networks:'), $txt];
824                         }
825
826                         if ($txt = prepare_text($a->profile['music'])) {
827                                 $profile['music'] = [L10n::t('Musical interests:'), $txt];
828                         }
829
830                         if ($txt = prepare_text($a->profile['book'])) {
831                                 $profile['book'] = [L10n::t('Books, literature:'), $txt];
832                         }
833
834                         if ($txt = prepare_text($a->profile['tv'])) {
835                                 $profile['tv'] = [L10n::t('Television:'), $txt];
836                         }
837
838                         if ($txt = prepare_text($a->profile['film'])) {
839                                 $profile['film'] = [L10n::t('Film/dance/culture/entertainment:'), $txt];
840                         }
841
842                         if ($txt = prepare_text($a->profile['romance'])) {
843                                 $profile['romance'] = [L10n::t('Love/Romance:'), $txt];
844                         }
845
846                         if ($txt = prepare_text($a->profile['work'])) {
847                                 $profile['work'] = [L10n::t('Work/employment:'), $txt];
848                         }
849
850                         if ($txt = prepare_text($a->profile['education'])) {
851                                 $profile['education'] = [L10n::t('School/education:'), $txt];
852                         }
853
854                         //show subcribed forum if it is enabled in the usersettings
855                         if (Feature::isEnabled($uid, 'forumlist_profile')) {
856                                 $profile['forumlist'] = [L10n::t('Forums:'), ForumManager::profileAdvanced($uid)];
857                         }
858
859                         if ($a->profile['uid'] == local_user()) {
860                                 $profile['edit'] = [System::baseUrl() . '/profiles/' . $a->profile['id'], L10n::t('Edit profile'), '', L10n::t('Edit profile')];
861                         }
862
863                         return replace_macros($tpl, [
864                                 '$title' => L10n::t('Profile'),
865                                 '$basic' => L10n::t('Basic'),
866                                 '$advanced' => L10n::t('Advanced'),
867                                 '$profile' => $profile
868                         ]);
869                 }
870
871                 return '';
872         }
873
874         public static function getTabs($a, $is_owner = false, $nickname = null)
875         {
876                 if (is_null($nickname)) {
877                         $nickname = $a->user['nickname'];
878                 }
879
880                 $tab = false;
881                 if (x($_GET, 'tab')) {
882                         $tab = notags(trim($_GET['tab']));
883                 }
884
885                 $url = System::baseUrl() . '/profile/' . $nickname;
886
887                 $tabs = [
888                         [
889                                 'label' => L10n::t('Status'),
890                                 'url'   => $url,
891                                 'sel'   => !$tab && $a->argv[0] == 'profile' ? 'active' : '',
892                                 'title' => L10n::t('Status Messages and Posts'),
893                                 'id'    => 'status-tab',
894                                 'accesskey' => 'm',
895                         ],
896                         [
897                                 'label' => L10n::t('Profile'),
898                                 'url'   => $url . '/?tab=profile',
899                                 'sel'   => $tab == 'profile' ? 'active' : '',
900                                 'title' => L10n::t('Profile Details'),
901                                 'id'    => 'profile-tab',
902                                 'accesskey' => 'r',
903                         ],
904                         [
905                                 'label' => L10n::t('Photos'),
906                                 'url'   => System::baseUrl() . '/photos/' . $nickname,
907                                 'sel'   => !$tab && $a->argv[0] == 'photos' ? 'active' : '',
908                                 'title' => L10n::t('Photo Albums'),
909                                 'id'    => 'photo-tab',
910                                 'accesskey' => 'h',
911                         ],
912                         [
913                                 'label' => L10n::t('Videos'),
914                                 'url'   => System::baseUrl() . '/videos/' . $nickname,
915                                 'sel'   => !$tab && $a->argv[0] == 'videos' ? 'active' : '',
916                                 'title' => L10n::t('Videos'),
917                                 'id'    => 'video-tab',
918                                 'accesskey' => 'v',
919                         ],
920                 ];
921
922                 // the calendar link for the full featured events calendar
923                 if ($is_owner && $a->theme_events_in_profile) {
924                         $tabs[] = [
925                                 'label' => L10n::t('Events'),
926                                 'url'   => System::baseUrl() . '/events',
927                                 'sel'   => !$tab && $a->argv[0] == 'events' ? 'active' : '',
928                                 'title' => L10n::t('Events and Calendar'),
929                                 'id'    => 'events-tab',
930                                 'accesskey' => 'e',
931                         ];
932                         // if the user is not the owner of the calendar we only show a calendar
933                         // with the public events of the calendar owner
934                 } elseif (!$is_owner) {
935                         $tabs[] = [
936                                 'label' => L10n::t('Events'),
937                                 'url'   => System::baseUrl() . '/cal/' . $nickname,
938                                 'sel'   => !$tab && $a->argv[0] == 'cal' ? 'active' : '',
939                                 'title' => L10n::t('Events and Calendar'),
940                                 'id'    => 'events-tab',
941                                 'accesskey' => 'e',
942                         ];
943                 }
944
945                 if ($is_owner) {
946                         $tabs[] = [
947                                 'label' => L10n::t('Personal Notes'),
948                                 'url'   => System::baseUrl() . '/notes',
949                                 'sel'   => !$tab && $a->argv[0] == 'notes' ? 'active' : '',
950                                 'title' => L10n::t('Only You Can See This'),
951                                 'id'    => 'notes-tab',
952                                 'accesskey' => 't',
953                         ];
954                 }
955
956                 if (!empty($_SESSION['new_member']) && $is_owner) {
957                         $tabs[] = [
958                                 'label' => L10n::t('Tips for New Members'),
959                                 'url'   => System::baseUrl() . '/newmember',
960                                 'sel'   => false,
961                                 'title' => L10n::t('Tips for New Members'),
962                                 'id'    => 'newmember-tab',
963                         ];
964                 }
965
966                 if (!$is_owner && empty($a->profile['hide-friends'])) {
967                         $tabs[] = [
968                                 'label' => L10n::t('Contacts'),
969                                 'url'   => System::baseUrl() . '/viewcontacts/' . $nickname,
970                                 'sel'   => !$tab && $a->argv[0] == 'viewcontacts' ? 'active' : '',
971                                 'title' => L10n::t('Contacts'),
972                                 'id'    => 'viewcontacts-tab',
973                                 'accesskey' => 'k',
974                         ];
975                 }
976
977                 $arr = ['is_owner' => $is_owner, 'nickname' => $nickname, 'tab' => $tab, 'tabs' => $tabs];
978                 Addon::callHooks('profile_tabs', $arr);
979
980                 $tpl = get_markup_template('common_tabs.tpl');
981
982                 return replace_macros($tpl, ['$tabs' => $arr['tabs']]);
983         }
984
985         /**
986          * Retrieves the my_url session variable
987          *
988          * @return string
989          */
990         public static function getMyURL()
991         {
992                 if (x($_SESSION, 'my_url')) {
993                         return $_SESSION['my_url'];
994                 }
995                 return null;
996         }
997
998         /**
999          * Process the 'zrl' parameter and initiate the remote authentication.
1000          *
1001          * This method checks if the visitor has a public contact entry and
1002          * redirects the visitor to his/her instance to start the magic auth (Authentication)
1003          * process.
1004          *
1005          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/channel.php
1006          *
1007          * @param App $a Application instance.
1008          */
1009         public static function zrlInit(App $a)
1010         {
1011                 $my_url = self::getMyURL();
1012                 $my_url = Network::isUrlValid($my_url);
1013
1014                 if (empty($my_url) || local_user()) {
1015                         return;
1016                 }
1017
1018                 $arr = ['zrl' => $my_url, 'url' => $a->cmd];
1019                 Addon::callHooks('zrl_init', $arr);
1020
1021                 // Try to find the public contact entry of the visitor.
1022                 $cid = Contact::getIdForURL($my_url);
1023                 if (!$cid) {
1024                         Logger::log('No contact record found for ' . $my_url, LOGGER_DEBUG);
1025                         return;
1026                 }
1027
1028                 $contact = DBA::selectFirst('contact',['id', 'url'], ['id' => $cid]);
1029
1030                 if (DBA::isResult($contact) && remote_user() && remote_user() == $contact['id']) {
1031                         Logger::log('The visitor ' . $my_url . ' is already authenticated', LOGGER_DEBUG);
1032                         return;
1033                 }
1034
1035                 // Avoid endless loops
1036                 $cachekey = 'zrlInit:' . $my_url;
1037                 if (Cache::get($cachekey)) {
1038                         Logger::log('URL ' . $my_url . ' already tried to authenticate.', LOGGER_DEBUG);
1039                         return;
1040                 } else {
1041                         Cache::set($cachekey, true, Cache::MINUTE);
1042                 }
1043
1044                 Logger::log('Not authenticated. Invoking reverse magic-auth for ' . $my_url, LOGGER_DEBUG);
1045
1046                 Worker::add(PRIORITY_LOW, 'GProbe', $my_url);
1047
1048                 // Try to avoid recursion - but send them home to do a proper magic auth.
1049                 $query = str_replace(array('?zrl=', '&zid='), array('?rzrl=', '&rzrl='), $a->query_string);
1050                 // The other instance needs to know where to redirect.
1051                 $dest = urlencode($a->getBaseURL() . '/' . $query);
1052
1053                 // We need to extract the basebath from the profile url
1054                 // to redirect the visitors '/magic' module.
1055                 // Note: We should have the basepath of a contact also in the contact table.
1056                 $urlarr = explode('/profile/', $contact['url']);
1057                 $basepath = $urlarr[0];
1058
1059                 if ($basepath != $a->getBaseURL() && !strstr($dest, '/magic') && !strstr($dest, '/rmagic')) {
1060                         $magic_path = $basepath . '/magic' . '?f=&owa=1&dest=' . $dest;
1061
1062                         // We have to check if the remote server does understand /magic without invoking something
1063                         $serverret = Network::curl($basepath . '/magic');
1064                         if ($serverret->isSuccess()) {
1065                                 Logger::log('Doing magic auth for visitor ' . $my_url . ' to ' . $magic_path, LOGGER_DEBUG);
1066                                 System::externalRedirect($magic_path);
1067                         }
1068                 }
1069         }
1070
1071         /**
1072          * OpenWebAuth authentication.
1073          *
1074          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/zid.php
1075          *
1076          * @param string $token
1077          */
1078         public static function openWebAuthInit($token)
1079         {
1080                 $a = get_app();
1081
1082                 // Clean old OpenWebAuthToken entries.
1083                 OpenWebAuthToken::purge('owt', '3 MINUTE');
1084
1085                 // Check if the token we got is the same one
1086                 // we have stored in the database.
1087                 $visitor_handle = OpenWebAuthToken::getMeta('owt', 0, $token);
1088
1089                 if($visitor_handle === false) {
1090                         return;
1091                 }
1092
1093                 // Try to find the public contact entry of the visitor.
1094                 $cid = Contact::getIdForURL($visitor_handle);
1095                 if(!$cid) {
1096                         Logger::log('owt: unable to finger ' . $visitor_handle, LOGGER_DEBUG);
1097                         return;
1098                 }
1099
1100                 $visitor = DBA::selectFirst('contact', [], ['id' => $cid]);
1101
1102                 // Authenticate the visitor.
1103                 $_SESSION['authenticated'] = 1;
1104                 $_SESSION['visitor_id'] = $visitor['id'];
1105                 $_SESSION['visitor_handle'] = $visitor['addr'];
1106                 $_SESSION['visitor_home'] = $visitor['url'];
1107                 $_SESSION['my_url'] = $visitor['url'];
1108
1109                 $arr = [
1110                         'visitor' => $visitor,
1111                         'url' => $a->query_string
1112                 ];
1113                 /**
1114                  * @hooks magic_auth_success
1115                  *   Called when a magic-auth was successful.
1116                  *   * \e array \b visitor
1117                  *   * \e string \b url
1118                  */
1119                 Addon::callHooks('magic_auth_success', $arr);
1120
1121                 $a->contact = $arr['visitor'];
1122
1123                 info(L10n::t('OpenWebAuth: %1$s welcomes %2$s', $a->getHostName(), $visitor['name']));
1124
1125                 Logger::log('OpenWebAuth: auth success from ' . $visitor['addr'], LOGGER_DEBUG);
1126         }
1127
1128         public static function zrl($s, $force = false)
1129         {
1130                 if (!strlen($s)) {
1131                         return $s;
1132                 }
1133                 if ((!strpos($s, '/profile/')) && (!$force)) {
1134                         return $s;
1135                 }
1136                 if ($force && substr($s, -1, 1) !== '/') {
1137                         $s = $s . '/';
1138                 }
1139                 $achar = strpos($s, '?') ? '&' : '?';
1140                 $mine = self::getMyURL();
1141                 if ($mine && !link_compare($mine, $s)) {
1142                         return $s . $achar . 'zrl=' . urlencode($mine);
1143                 }
1144                 return $s;
1145         }
1146
1147         /**
1148          * Get the user ID of the page owner.
1149          *
1150          * Used from within PCSS themes to set theme parameters. If there's a
1151          * puid request variable, that is the "page owner" and normally their theme
1152          * settings take precedence; unless a local user sets the "always_my_theme"
1153          * system pconfig, which means they don't want to see anybody else's theme
1154          * settings except their own while on this site.
1155          *
1156          * @brief Get the user ID of the page owner
1157          * @return int user ID
1158          *
1159          * @note Returns local_user instead of user ID if "always_my_theme"
1160          *      is set to true
1161          */
1162         public static function getThemeUid()
1163         {
1164                 $uid = ((!empty($_REQUEST['puid'])) ? intval($_REQUEST['puid']) : 0);
1165                 if ((local_user()) && ((PConfig::get(local_user(), 'system', 'always_my_theme')) || (!$uid))) {
1166                         return local_user();
1167                 }
1168
1169                 return $uid;
1170         }
1171
1172         /**
1173         * Stip zrl parameter from a string.
1174         *
1175         * @param string $s The input string.
1176         * @return string The zrl.
1177         */
1178         public static function stripZrls($s)
1179         {
1180                 return preg_replace('/[\?&]zrl=(.*?)([\?&]|$)/is', '', $s);
1181         }
1182
1183         /**
1184         * Stip query parameter from a string.
1185         *
1186         * @param string $s The input string.
1187         * @return string The query parameter.
1188         */
1189         public static function stripQueryParam($s, $param)
1190         {
1191                 return preg_replace('/[\?&]' . $param . '=(.*?)(&|$)/ism', '$2', $s);
1192         }
1193 }