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.

1195 lines
35 KiB

  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\Temporal;
  28. require_once 'include/dba.php';
  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 object $a App
  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 = x($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'] = format_network_name($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 = normalise_link($profile['url']);
  282. } else {
  283. $profile_url = normalise_link(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. if ($connect && ($profile['network'] != Protocol::DFRN) && !isset($profile['remoteconnect'])) {
  290. $connect = false;
  291. }
  292. $remoteconnect = null;
  293. if (isset($profile['remoteconnect'])) {
  294. $remoteconnect = $profile['remoteconnect'];
  295. }
  296. if ($connect && ($profile['network'] == Protocol::DFRN) && !isset($remoteconnect)) {
  297. $subscribe_feed = L10n::t('Atom feed');
  298. } else {
  299. $subscribe_feed = false;
  300. }
  301. $wallmessage = false;
  302. $wallmessage_link = false;
  303. // See issue https://github.com/friendica/friendica/issues/3838
  304. // Either we remove the message link for remote users or we enable creating messages from remote users
  305. if (remote_user() || (self::getMyURL() && x($profile, 'unkmail') && ($profile['uid'] != local_user()))) {
  306. $wallmessage = L10n::t('Message');
  307. if (remote_user()) {
  308. $r = q(
  309. "SELECT `url` FROM `contact` WHERE `uid` = %d AND `id` = '%s' AND `rel` = %d",
  310. intval($profile['uid']),
  311. intval(remote_user()),
  312. intval(Contact::FRIEND)
  313. );
  314. } else {
  315. $r = q(
  316. "SELECT `url` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `rel` = %d",
  317. intval($profile['uid']),
  318. DBA::escape(normalise_link(self::getMyURL())),
  319. intval(Contact::FRIEND)
  320. );
  321. }
  322. if ($r) {
  323. $remote_url = $r[0]['url'];
  324. $message_path = preg_replace('=(.*)/profile/(.*)=ism', '$1/message/new/', $remote_url);
  325. $wallmessage_link = $message_path . base64_encode(defaults($profile, 'addr', ''));
  326. } else if (!empty($profile['nickname'])) {
  327. $wallmessage_link = 'wallmessage/' . $profile['nickname'];
  328. }
  329. }
  330. // show edit profile to yourself
  331. if (!$is_contact && $profile['uid'] == local_user() && Feature::isEnabled(local_user(), 'multi_profiles')) {
  332. $profile['edit'] = [System::baseUrl() . '/profiles', L10n::t('Profiles'), '', L10n::t('Manage/edit profiles')];
  333. $r = q(
  334. "SELECT * FROM `profile` WHERE `uid` = %d",
  335. local_user()
  336. );
  337. $profile['menu'] = [
  338. 'chg_photo' => L10n::t('Change profile photo'),
  339. 'cr_new' => L10n::t('Create New Profile'),
  340. 'entries' => [],
  341. ];
  342. if (DBA::isResult($r)) {
  343. foreach ($r as $rr) {
  344. $profile['menu']['entries'][] = [
  345. 'photo' => $rr['thumb'],
  346. 'id' => $rr['id'],
  347. 'alt' => L10n::t('Profile Image'),
  348. 'profile_name' => $rr['profile-name'],
  349. 'isdefault' => $rr['is-default'],
  350. 'visibile_to_everybody' => L10n::t('visible to everybody'),
  351. 'edit_visibility' => L10n::t('Edit visibility'),
  352. ];
  353. }
  354. }
  355. }
  356. if (!$is_contact && $profile['uid'] == local_user() && !Feature::isEnabled(local_user(), 'multi_profiles')) {
  357. $profile['edit'] = [System::baseUrl() . '/profiles/' . $profile['id'], L10n::t('Edit profile'), '', L10n::t('Edit profile')];
  358. $profile['menu'] = [
  359. 'chg_photo' => L10n::t('Change profile photo'),
  360. 'cr_new' => null,
  361. 'entries' => [],
  362. ];
  363. }
  364. // Fetch the account type
  365. $account_type = Contact::getAccountType($profile);
  366. if (x($profile, 'address')
  367. || x($profile, 'location')
  368. || x($profile, 'locality')
  369. || x($profile, 'region')
  370. || x($profile, 'postal-code')
  371. || x($profile, 'country-name')
  372. ) {
  373. $location = L10n::t('Location:');
  374. }
  375. $gender = x($profile, 'gender') ? L10n::t('Gender:') : false;
  376. $marital = x($profile, 'marital') ? L10n::t('Status:') : false;
  377. $homepage = x($profile, 'homepage') ? L10n::t('Homepage:') : false;
  378. $about = x($profile, 'about') ? L10n::t('About:') : false;
  379. $xmpp = x($profile, 'xmpp') ? L10n::t('XMPP:') : false;
  380. if ((x($profile, 'hidewall') || $block) && !local_user() && !remote_user()) {
  381. $location = $gender = $marital = $homepage = $about = false;
  382. }
  383. $split_name = Diaspora::splitName($profile['name']);
  384. $firstname = $split_name['first'];
  385. $lastname = $split_name['last'];
  386. if (x($profile, 'guid')) {
  387. $diaspora = [
  388. 'guid' => $profile['guid'],
  389. 'podloc' => System::baseUrl(),
  390. 'searchable' => (($profile['publish'] && $profile['net-publish']) ? 'true' : 'false' ),
  391. 'nickname' => $profile['nickname'],
  392. 'fullname' => $profile['name'],
  393. 'firstname' => $firstname,
  394. 'lastname' => $lastname,
  395. 'photo300' => defaults($profile, 'contact_photo', ''),
  396. 'photo100' => defaults($profile, 'contact_thumb', ''),
  397. 'photo50' => defaults($profile, 'contact_micro', ''),
  398. ];
  399. } else {
  400. $diaspora = false;
  401. }
  402. $contact_block = '';
  403. $updated = '';
  404. $contacts = 0;
  405. if (!$block) {
  406. $contact_block = HTML::contactBlock();
  407. if (is_array($a->profile) && !$a->profile['hide-friends']) {
  408. $r = q(
  409. "SELECT `gcontact`.`updated` FROM `contact` INNER JOIN `gcontact` WHERE `gcontact`.`nurl` = `contact`.`nurl` AND `self` AND `uid` = %d LIMIT 1",
  410. intval($a->profile['uid'])
  411. );
  412. if (DBA::isResult($r)) {
  413. $updated = date('c', strtotime($r[0]['updated']));
  414. }
  415. $r = q(
  416. "SELECT COUNT(*) AS `total` FROM `contact`
  417. WHERE `uid` = %d
  418. AND NOT `self` AND NOT `blocked` AND NOT `pending`
  419. AND NOT `hidden` AND NOT `archive`
  420. AND `network` IN ('%s', '%s', '%s', '')",
  421. intval($profile['uid']),
  422. DBA::escape(Protocol::DFRN),
  423. DBA::escape(Protocol::DIASPORA),
  424. DBA::escape(Protocol::OSTATUS)
  425. );
  426. if (DBA::isResult($r)) {
  427. $contacts = intval($r[0]['total']);
  428. }
  429. }
  430. }
  431. $p = [];
  432. foreach ($profile as $k => $v) {
  433. $k = str_replace('-', '_', $k);
  434. $p[$k] = $v;
  435. }
  436. if (isset($p['about'])) {
  437. $p['about'] = BBCode::convert($p['about']);
  438. }
  439. if (isset($p['address'])) {
  440. $p['address'] = BBCode::convert($p['address']);
  441. } elseif (isset($p['location'])) {
  442. $p['address'] = BBCode::convert($p['location']);
  443. }
  444. if (isset($p['photo'])) {
  445. $p['photo'] = ProxyUtils::proxifyUrl($p['photo'], false, ProxyUtils::SIZE_SMALL);
  446. }
  447. $p['url'] = Contact::magicLink(defaults($p, 'url', $profile_url));
  448. $tpl = Renderer::getMarkupTemplate('profile_vcard.tpl');
  449. $o .= Renderer::replaceMacros($tpl, [
  450. '$profile' => $p,
  451. '$xmpp' => $xmpp,
  452. '$connect' => $connect,
  453. '$remoteconnect' => $remoteconnect,
  454. '$subscribe_feed' => $subscribe_feed,
  455. '$wallmessage' => $wallmessage,
  456. '$wallmessage_link' => $wallmessage_link,
  457. '$account_type' => $account_type,
  458. '$location' => $location,
  459. '$gender' => $gender,
  460. '$marital' => $marital,
  461. '$homepage' => $homepage,
  462. '$about' => $about,
  463. '$network' => L10n::t('Network:'),
  464. '$contacts' => $contacts,
  465. '$updated' => $updated,
  466. '$diaspora' => $diaspora,
  467. '$contact_block' => $contact_block,
  468. ]);
  469. $arr = ['profile' => &$profile, 'entry' => &$o];
  470. Addon::callHooks('profile_sidebar', $arr);
  471. return $o;
  472. }
  473. public static function getBirthdays()
  474. {
  475. $a = get_app();
  476. $o = '';
  477. if (!local_user() || $a->is_mobile || $a->is_tablet) {
  478. return $o;
  479. }
  480. /*
  481. * $mobile_detect = new Mobile_Detect();
  482. * $is_mobile = $mobile_detect->isMobile() || $mobile_detect->isTablet();
  483. * if ($is_mobile)
  484. * return $o;
  485. */
  486. $bd_format = L10n::t('g A l F d'); // 8 AM Friday January 18
  487. $bd_short = L10n::t('F d');
  488. $cachekey = 'get_birthdays:' . local_user();
  489. $r = Cache::get($cachekey);
  490. if (is_null($r)) {
  491. $s = DBA::p(
  492. "SELECT `event`.*, `event`.`id` AS `eid`, `contact`.* FROM `event`
  493. INNER JOIN `contact` ON `contact`.`id` = `event`.`cid`
  494. WHERE `event`.`uid` = ? AND `type` = 'birthday' AND `start` < ? AND `finish` > ?
  495. ORDER BY `start` ASC ",
  496. local_user(),
  497. DateTimeFormat::utc('now + 6 days'),
  498. DateTimeFormat::utcNow()
  499. );
  500. if (DBA::isResult($s)) {
  501. $r = DBA::toArray($s);
  502. Cache::set($cachekey, $r, Cache::HOUR);
  503. }
  504. }
  505. $total = 0;
  506. $classtoday = '';
  507. if (DBA::isResult($r)) {
  508. $now = strtotime('now');
  509. $cids = [];
  510. $istoday = false;
  511. foreach ($r as $rr) {
  512. if (strlen($rr['name'])) {
  513. $total ++;
  514. }
  515. if ((strtotime($rr['start'] . ' +00:00') < $now) && (strtotime($rr['finish'] . ' +00:00') > $now)) {
  516. $istoday = true;
  517. }
  518. }
  519. $classtoday = $istoday ? ' birthday-today ' : '';
  520. if ($total) {
  521. foreach ($r as &$rr) {
  522. if (!strlen($rr['name'])) {
  523. continue;
  524. }
  525. // avoid duplicates
  526. if (in_array($rr['cid'], $cids)) {
  527. continue;
  528. }
  529. $cids[] = $rr['cid'];
  530. $today = (((strtotime($rr['start'] . ' +00:00') < $now) && (strtotime($rr['finish'] . ' +00:00') > $now)) ? true : false);
  531. $rr['link'] = Contact::magicLink($rr['url']);
  532. $rr['title'] = $rr['name'];
  533. $rr['date'] = day_translate(DateTimeFormat::convert($rr['start'], $a->timezone, 'UTC', $rr['adjust'] ? $bd_format : $bd_short)) . (($today) ? ' ' . L10n::t('[today]') : '');
  534. $rr['startime'] = null;
  535. $rr['today'] = $today;
  536. }
  537. }
  538. }
  539. $tpl = Renderer::getMarkupTemplate('birthdays_reminder.tpl');
  540. return Renderer::replaceMacros($tpl, [
  541. '$baseurl' => System::baseUrl(),
  542. '$classtoday' => $classtoday,
  543. '$count' => $total,
  544. '$event_reminders' => L10n::t('Birthday Reminders'),
  545. '$event_title' => L10n::t('Birthdays this week:'),
  546. '$events' => $r,
  547. '$lbr' => '{', // raw brackets mess up if/endif macro processing
  548. '$rbr' => '}'
  549. ]);
  550. }
  551. public static function getEventsReminderHTML()
  552. {
  553. $a = get_app();
  554. $o = '';
  555. if (!local_user() || $a->is_mobile || $a->is_tablet) {
  556. return $o;
  557. }
  558. /*
  559. * $mobile_detect = new Mobile_Detect();
  560. * $is_mobile = $mobile_detect->isMobile() || $mobile_detect->isTablet();
  561. * if ($is_mobile)
  562. * return $o;
  563. */
  564. $bd_format = L10n::t('g A l F d'); // 8 AM Friday January 18
  565. $classtoday = '';
  566. $condition = ["`uid` = ? AND `type` != 'birthday' AND `start` < ? AND `start` >= ?",
  567. local_user(), DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utc('now - 1 days')];
  568. $s = DBA::select('event', [], $condition, ['order' => ['start']]);
  569. $r = [];
  570. if (DBA::isResult($s)) {
  571. $istoday = false;
  572. $total = 0;
  573. while ($rr = DBA::fetch($s)) {
  574. $condition = ['parent-uri' => $rr['uri'], 'uid' => $rr['uid'], 'author-id' => public_contact(),
  575. 'activity' => [Item::activityToIndex(ACTIVITY_ATTEND), Item::activityToIndex(ACTIVITY_ATTENDMAYBE)],
  576. 'visible' => true, 'deleted' => false];
  577. if (!Item::exists($condition)) {
  578. continue;
  579. }
  580. if (strlen($rr['summary'])) {
  581. $total++;
  582. }
  583. $strt = DateTimeFormat::convert($rr['start'], $rr['adjust'] ? $a->timezone : 'UTC', 'UTC', 'Y-m-d');
  584. if ($strt === DateTimeFormat::timezoneNow($a->timezone, 'Y-m-d')) {
  585. $istoday = true;
  586. }
  587. $title = strip_tags(html_entity_decode(BBCode::convert($rr['summary']), ENT_QUOTES, 'UTF-8'));
  588. if (strlen($title) > 35) {
  589. $title = substr($title, 0, 32) . '... ';
  590. }
  591. $description = substr(strip_tags(BBCode::convert($rr['desc'])), 0, 32) . '... ';
  592. if (!$description) {
  593. $description = L10n::t('[No description]');
  594. }
  595. $strt = DateTimeFormat::convert($rr['start'], $rr['adjust'] ? $a->timezone : 'UTC');
  596. if (substr($strt, 0, 10) < DateTimeFormat::timezoneNow($a->timezone, 'Y-m-d')) {
  597. continue;
  598. }
  599. $today = ((substr($strt, 0, 10) === DateTimeFormat::timezoneNow($a->timezone, 'Y-m-d')) ? true : false);
  600. $rr['title'] = $title;
  601. $rr['description'] = $description;
  602. $rr['date'] = day_translate(DateTimeFormat::convert($rr['start'], $rr['adjust'] ? $a->timezone : 'UTC', 'UTC', $bd_format)) . (($today) ? ' ' . L10n::t('[today]') : '');
  603. $rr['startime'] = $strt;
  604. $rr['today'] = $today;
  605. $r[] = $rr;
  606. }
  607. DBA::close($s);
  608. $classtoday = (($istoday) ? 'event-today' : '');
  609. }
  610. $tpl = Renderer::getMarkupTemplate('events_reminder.tpl');
  611. return Renderer::replaceMacros($tpl, [
  612. '$baseurl' => System::baseUrl(),
  613. '$classtoday' => $classtoday,
  614. '$count' => count($r),
  615. '$event_reminders' => L10n::t('Event Reminders'),
  616. '$event_title' => L10n::t('Upcoming events the next 7 days:'),
  617. '$events' => $r,
  618. ]);
  619. }
  620. public static function getAdvanced(App $a)
  621. {
  622. $o = '';
  623. $uid = $a->profile['uid'];
  624. $o .= Renderer::replaceMacros(
  625. Renderer::getMarkupTemplate('section_title.tpl'),
  626. ['$title' => L10n::t('Profile')]
  627. );
  628. if ($a->profile['name']) {
  629. $tpl = Renderer::getMarkupTemplate('profile_advanced.tpl');
  630. $profile = [];
  631. $profile['fullname'] = [L10n::t('Full Name:'), $a->profile['name']];
  632. if (Feature::isEnabled($uid, 'profile_membersince')) {
  633. $profile['membersince'] = [L10n::t('Member since:'), DateTimeFormat::local($a->profile['register_date'])];
  634. }
  635. if ($a->profile['gender']) {
  636. $profile['gender'] = [L10n::t('Gender:'), $a->profile['gender']];
  637. }
  638. if (($a->profile['dob']) && ($a->profile['dob'] > '0001-01-01')) {
  639. $year_bd_format = L10n::t('j F, Y');
  640. $short_bd_format = L10n::t('j F');
  641. $val = day_translate(
  642. intval($a->profile['dob']) ?
  643. DateTimeFormat::utc($a->profile['dob'] . ' 00:00 +00:00', $year_bd_format)
  644. : DateTimeFormat::utc('2001-' . substr($a->profile['dob'], 5) . ' 00:00 +00:00', $short_bd_format)
  645. );
  646. $profile['birthday'] = [L10n::t('Birthday:'), $val];
  647. }
  648. if (!empty($a->profile['dob'])
  649. && $a->profile['dob'] > '0001-01-01'
  650. && $age = Temporal::getAgeByTimezone($a->profile['dob'], $a->profile['timezone'], '')
  651. ) {
  652. $profile['age'] = [L10n::t('Age:'), $age];
  653. }
  654. if ($a->profile['marital']) {
  655. $profile['marital'] = [L10n::t('Status:'), $a->profile['marital']];
  656. }
  657. /// @TODO Maybe use x() here, plus below?
  658. if ($a->profile['with']) {
  659. $profile['marital']['with'] = $a->profile['with'];
  660. }
  661. if (strlen($a->profile['howlong']) && $a->profile['howlong'] >= DBA::NULL_DATETIME) {
  662. $profile['howlong'] = Temporal::getRelativeDate($a->profile['howlong'], L10n::t('for %1$d %2$s'));
  663. }
  664. if ($a->profile['sexual']) {
  665. $profile['sexual'] = [L10n::t('Sexual Preference:'), $a->profile['sexual']];
  666. }
  667. if ($a->profile['homepage']) {
  668. $profile['homepage'] = [L10n::t('Homepage:'), HTML::toLink($a->profile['homepage'])];
  669. }
  670. if ($a->profile['hometown']) {
  671. $profile['hometown'] = [L10n::t('Hometown:'), HTML::toLink($a->profile['hometown'])];
  672. }
  673. if ($a->profile['pub_keywords']) {
  674. $profile['pub_keywords'] = [L10n::t('Tags:'), $a->profile['pub_keywords']];
  675. }
  676. if ($a->profile['politic']) {
  677. $profile['politic'] = [L10n::t('Political Views:'), $a->profile['politic']];
  678. }
  679. if ($a->profile['religion']) {
  680. $profile['religion'] = [L10n::t('Religion:'), $a->profile['religion']];
  681. }
  682. if ($txt = prepare_text($a->profile['about'])) {
  683. $profile['about'] = [L10n::t('About:'), $txt];
  684. }
  685. if ($txt = prepare_text($a->profile['interest'])) {
  686. $profile['interest'] = [L10n::t('Hobbies/Interests:'), $txt];
  687. }
  688. if ($txt = prepare_text($a->profile['likes'])) {
  689. $profile['likes'] = [L10n::t('Likes:'), $txt];
  690. }
  691. if ($txt = prepare_text($a->profile['dislikes'])) {
  692. $profile['dislikes'] = [L10n::t('Dislikes:'), $txt];
  693. }
  694. if ($txt = prepare_text($a->profile['contact'])) {
  695. $profile['contact'] = [L10n::t('Contact information and Social Networks:'), $txt];
  696. }
  697. if ($txt = prepare_text($a->profile['music'])) {
  698. $profile['music'] = [L10n::t('Musical interests:'), $txt];
  699. }
  700. if ($txt = prepare_text($a->profile['book'])) {
  701. $profile['book'] = [L10n::t('Books, literature:'), $txt];
  702. }
  703. if ($txt = prepare_text($a->profile['tv'])) {
  704. $profile['tv'] = [L10n::t('Television:'), $txt];
  705. }
  706. if ($txt = prepare_text($a->profile['film'])) {
  707. $profile['film'] = [L10n::t('Film/dance/culture/entertainment:'), $txt];
  708. }
  709. if ($txt = prepare_text($a->profile['romance'])) {
  710. $profile['romance'] = [L10n::t('Love/Romance:'), $txt];
  711. }
  712. if ($txt = prepare_text($a->profile['work'])) {
  713. $profile['work'] = [L10n::t('Work/employment:'), $txt];
  714. }
  715. if ($txt = prepare_text($a->profile['education'])) {
  716. $profile['education'] = [L10n::t('School/education:'), $txt];
  717. }
  718. //show subcribed forum if it is enabled in the usersettings
  719. if (Feature::isEnabled($uid, 'forumlist_profile')) {
  720. $profile['forumlist'] = [L10n::t('Forums:'), ForumManager::profileAdvanced($uid)];
  721. }
  722. if ($a->profile['uid'] == local_user()) {
  723. $profile['edit'] = [System::baseUrl() . '/profiles/' . $a->profile['id'], L10n::t('Edit profile'), '', L10n::t('Edit profile')];
  724. }
  725. return Renderer::replaceMacros($tpl, [
  726. '$title' => L10n::t('Profile'),
  727. '$basic' => L10n::t('Basic'),
  728. '$advanced' => L10n::t('Advanced'),
  729. '$profile' => $profile
  730. ]);
  731. }
  732. return '';
  733. }
  734. public static function getTabs($a, $is_owner = false, $nickname = null)
  735. {
  736. if (is_null($nickname)) {
  737. $nickname = $a->user['nickname'];
  738. }
  739. $tab = false;
  740. if (x($_GET, 'tab')) {
  741. $tab = notags(trim($_GET['tab']));
  742. }
  743. $url = System::baseUrl() . '/profile/' . $nickname;
  744. $tabs = [
  745. [
  746. 'label' => L10n::t('Status'),
  747. 'url' => $url,
  748. 'sel' => !$tab && $a->argv[0] == 'profile' ? 'active' : '',
  749. 'title' => L10n::t('Status Messages and Posts'),
  750. 'id' => 'status-tab',
  751. 'accesskey' => 'm',
  752. ],
  753. [
  754. 'label' => L10n::t('Profile'),
  755. 'url' => $url . '/?tab=profile',
  756. 'sel' => $tab == 'profile' ? 'active' : '',
  757. 'title' => L10n::t('Profile Details'),
  758. 'id' => 'profile-tab',
  759. 'accesskey' => 'r',
  760. ],
  761. [
  762. 'label' => L10n::t('Photos'),
  763. 'url' => System::baseUrl() . '/photos/' . $nickname,
  764. 'sel' => !$tab && $a->argv[0] == 'photos' ? 'active' : '',
  765. 'title' => L10n::t('Photo Albums'),
  766. 'id' => 'photo-tab',
  767. 'accesskey' => 'h',
  768. ],
  769. [
  770. 'label' => L10n::t('Videos'),
  771. 'url' => System::baseUrl() . '/videos/' . $nickname,
  772. 'sel' => !$tab && $a->argv[0] == 'videos' ? 'active' : '',
  773. 'title' => L10n::t('Videos'),
  774. 'id' => 'video-tab',
  775. 'accesskey' => 'v',
  776. ],
  777. ];
  778. // the calendar link for the full featured events calendar
  779. if ($is_owner && $a->theme_events_in_profile) {
  780. $tabs[] = [
  781. 'label' => L10n::t('Events'),
  782. 'url' => System::baseUrl() . '/events',
  783. 'sel' => !$tab && $a->argv[0] == 'events' ? 'active' : '',
  784. 'title' => L10n::t('Events and Calendar'),
  785. 'id' => 'events-tab',
  786. 'accesskey' => 'e',
  787. ];
  788. // if the user is not the owner of the calendar we only show a calendar
  789. // with the public events of the calendar owner
  790. } elseif (!$is_owner) {
  791. $tabs[] = [
  792. 'label' => L10n::t('Events'),
  793. 'url' => System::baseUrl() . '/cal/' . $nickname,
  794. 'sel' => !$tab && $a->argv[0] == 'cal' ? 'active' : '',
  795. 'title' => L10n::t('Events and Calendar'),
  796. 'id' => 'events-tab',
  797. 'accesskey' => 'e',
  798. ];
  799. }
  800. if ($is_owner) {
  801. $tabs[] = [
  802. 'label' => L10n::t('Personal Notes'),
  803. 'url' => System::baseUrl() . '/notes',
  804. 'sel' => !$tab && $a->argv[0] == 'notes' ? 'active' : '',
  805. 'title' => L10n::t('Only You Can See This'),
  806. 'id' => 'notes-tab',
  807. 'accesskey' => 't',
  808. ];
  809. }
  810. if (!empty($_SESSION['new_member']) && $is_owner) {
  811. $tabs[] = [
  812. 'label' => L10n::t('Tips for New Members'),
  813. 'url' => System::baseUrl() . '/newmember',
  814. 'sel' => false,
  815. 'title' => L10n::t('Tips for New Members'),
  816. 'id' => 'newmember-tab',
  817. ];
  818. }
  819. if (!$is_owner && empty($a->profile['hide-friends'])) {
  820. $tabs[] = [
  821. 'label' => L10n::t('Contacts'),
  822. 'url' => System::baseUrl() . '/viewcontacts/' . $nickname,
  823. 'sel' => !$tab && $a->argv[0] == 'viewcontacts' ? 'active' : '',
  824. 'title' => L10n::t('Contacts'),
  825. 'id' => 'viewcontacts-tab',
  826. 'accesskey' => 'k',
  827. ];
  828. }
  829. $arr = ['is_owner' => $is_owner, 'nickname' => $nickname, 'tab' => $tab, 'tabs' => $tabs];
  830. Addon::callHooks('profile_tabs', $arr);
  831. $tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
  832. return Renderer::replaceMacros($tpl, ['$tabs' => $arr['tabs']]);
  833. }
  834. /**
  835. * Retrieves the my_url session variable
  836. *
  837. * @return string
  838. */
  839. public static function getMyURL()
  840. {
  841. if (x($_SESSION, 'my_url')) {
  842. return $_SESSION['my_url'];
  843. }
  844. return null;
  845. }
  846. /**
  847. * Process the 'zrl' parameter and initiate the remote authentication.
  848. *
  849. * This method checks if the visitor has a public contact entry and
  850. * redirects the visitor to his/her instance to start the magic auth (Authentication)
  851. * process.
  852. *
  853. * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/channel.php
  854. *
  855. * @param App $a Application instance.
  856. */
  857. public static function zrlInit(App $a)
  858. {
  859. $my_url = self::getMyURL();
  860. $my_url = Network::isUrlValid($my_url);
  861. if (empty($my_url) || local_user()) {
  862. return;
  863. }
  864. $arr = ['zrl' => $my_url, 'url' => $a->cmd];
  865. Addon::callHooks('zrl_init', $arr);
  866. // Try to find the public contact entry of the visitor.
  867. $cid = Contact::getIdForURL($my_url);
  868. if (!$cid) {
  869. Logger::log('No contact record found for ' . $my_url, Logger::DEBUG);
  870. return;
  871. }
  872. $contact = DBA::selectFirst('contact',['id', 'url'], ['id' => $cid]);
  873. if (DBA::isResult($contact) && remote_user() && remote_user() == $contact['id']) {
  874. Logger::log('The visitor ' . $my_url . ' is already authenticated', Logger::DEBUG);
  875. return;
  876. }
  877. // Avoid endless loops
  878. $cachekey = 'zrlInit:' . $my_url;
  879. if (Cache::get($cachekey)) {
  880. Logger::log('URL ' . $my_url . ' already tried to authenticate.', Logger::DEBUG);
  881. return;
  882. } else {
  883. Cache::set($cachekey, true, Cache::MINUTE);
  884. }
  885. Logger::log('Not authenticated. Invoking reverse magic-auth for ' . $my_url, Logger::DEBUG);
  886. Worker::add(PRIORITY_LOW, 'GProbe', $my_url);
  887. // Try to avoid recursion - but send them home to do a proper magic auth.
  888. $query = str_replace(array('?zrl=', '&zid='), array('?rzrl=', '&rzrl='), $a->query_string);
  889. // The other instance needs to know where to redirect.
  890. $dest = urlencode($a->getBaseURL() . '/' . $query);
  891. // We need to extract the basebath from the profile url
  892. // to redirect the visitors '/magic' module.
  893. // Note: We should have the basepath of a contact also in the contact table.
  894. $urlarr = explode('/profile/', $contact['url']);
  895. $basepath = $urlarr[0];
  896. if ($basepath != $a->getBaseURL() && !strstr($dest, '/magic') && !strstr($dest, '/rmagic')) {
  897. $magic_path = $basepath . '/magic' . '?f=&owa=1&dest=' . $dest;
  898. // We have to check if the remote server does understand /magic without invoking something
  899. $serverret = Network::curl($basepath . '/magic');
  900. if ($serverret->isSuccess()) {
  901. Logger::log('Doing magic auth for visitor ' . $my_url . ' to ' . $magic_path, Logger::DEBUG);
  902. System::externalRedirect($magic_path);
  903. }
  904. }
  905. }
  906. /**
  907. * OpenWebAuth authentication.
  908. *
  909. * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/zid.php
  910. *
  911. * @param string $token
  912. */
  913. public static function openWebAuthInit($token)
  914. {
  915. $a = get_app();
  916. // Clean old OpenWebAuthToken entries.
  917. OpenWebAuthToken::purge('owt', '3 MINUTE');
  918. // Check if the token we got is the same one
  919. // we have stored in the database.
  920. $visitor_handle = OpenWebAuthToken::getMeta('owt', 0, $token);
  921. if($visitor_handle === false) {
  922. return;
  923. }
  924. // Try to find the public contact entry of the visitor.
  925. $cid = Contact::getIdForURL($visitor_handle);
  926. if(!$cid) {
  927. Logger::log('owt: unable to finger ' . $visitor_handle, Logger::DEBUG);
  928. return;
  929. }
  930. $visitor = DBA::selectFirst('contact', [], ['id' => $cid]);
  931. // Authenticate the visitor.
  932. $_SESSION['authenticated'] = 1;
  933. $_SESSION['visitor_id'] = $visitor['id'];
  934. $_SESSION['visitor_handle'] = $visitor['addr'];
  935. $_SESSION['visitor_home'] = $visitor['url'];
  936. $_SESSION['my_url'] = $visitor['url'];
  937. $arr = [
  938. 'visitor' => $visitor,
  939. 'url' => $a->query_string
  940. ];
  941. /**
  942. * @hooks magic_auth_success
  943. * Called when a magic-auth was successful.
  944. * * \e array \b visitor
  945. * * \e string \b url
  946. */
  947. Addon::callHooks('magic_auth_success', $arr);
  948. $a->contact = $arr['visitor'];
  949. info(L10n::t('OpenWebAuth: %1$s welcomes %2$s', $a->getHostName(), $visitor['name']));
  950. Logger::log('OpenWebAuth: auth success from ' . $visitor['addr'], Logger::DEBUG);
  951. }
  952. public static function zrl($s, $force = false)
  953. {
  954. if (!strlen($s)) {
  955. return $s;
  956. }
  957. if ((!strpos($s, '/profile/')) && (!$force)) {
  958. return $s;
  959. }
  960. if ($force && substr($s, -1, 1) !== '/') {
  961. $s = $s . '/';
  962. }
  963. $achar = strpos($s, '?') ? '&' : '?';
  964. $mine = self::getMyURL();
  965. if ($mine && !link_compare($mine, $s)) {
  966. return $s . $achar . 'zrl=' . urlencode($mine);
  967. }
  968. return $s;
  969. }
  970. /**
  971. * Get the user ID of the page owner.
  972. *
  973. * Used from within PCSS themes to set theme parameters. If there's a
  974. * puid request variable, that is the "page owner" and normally their theme
  975. * settings take precedence; unless a local user sets the "always_my_theme"
  976. * system pconfig, which means they don't want to see anybody else's theme
  977. * settings except their own while on this site.
  978. *
  979. * @brief Get the user ID of the page owner
  980. * @return int user ID
  981. *
  982. * @note Returns local_user instead of user ID if "always_my_theme"
  983. * is set to true
  984. */
  985. public static function getThemeUid()
  986. {
  987. $uid = ((!empty($_REQUEST['puid'])) ? intval($_REQUEST['puid']) : 0);
  988. if ((local_user()) && ((PConfig::get(local_user(), 'system', 'always_my_theme')) || (!$uid))) {
  989. return local_user();
  990. }
  991. return $uid;
  992. }
  993. /**
  994. * Stip zrl parameter from a string.
  995. *
  996. * @param string $s The input string.
  997. * @return string The zrl.
  998. */
  999. public static function stripZrls($s)
  1000. {
  1001. return preg_replace('/[\?&]zrl=(.*?)([\?&]|$)/is', '', $s);
  1002. }
  1003. /**
  1004. * Stip query parameter from a string.
  1005. *
  1006. * @param string $s The input string.
  1007. * @return string The query parameter.
  1008. */
  1009. public static function stripQueryParam($s, $param)
  1010. {
  1011. return preg_replace('/[\?&]' . $param . '=(.*?)(&|$)/ism', '$2', $s);
  1012. }
  1013. }