Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1220 lines
36KB

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