Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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