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.

1029 lines
32KB

  1. <?php
  2. /**
  3. * @file src/Model/Event.php
  4. */
  5. namespace Friendica\Model;
  6. use Friendica\BaseObject;
  7. use Friendica\Content\Text\BBCode;
  8. use Friendica\Core\Addon;
  9. use Friendica\Core\L10n;
  10. use Friendica\Core\Logger;
  11. use Friendica\Core\PConfig;
  12. use Friendica\Core\Renderer;
  13. use Friendica\Core\System;
  14. use Friendica\Database\DBA;
  15. use Friendica\Model\Contact;
  16. use Friendica\Util\DateTimeFormat;
  17. use Friendica\Util\Map;
  18. use Friendica\Util\XML;
  19. /**
  20. * @brief functions for interacting with the event database table
  21. */
  22. class Event extends BaseObject
  23. {
  24. public static function getHTML(array $event, $simple = false)
  25. {
  26. if (empty($event)) {
  27. return '';
  28. }
  29. $bd_format = L10n::t('l F d, Y \@ g:i A'); // Friday January 18, 2011 @ 8 AM.
  30. $event_start = L10n::getDay(
  31. !empty($event['adjust']) ?
  32. DateTimeFormat::local($event['start'], $bd_format) : DateTimeFormat::utc($event['start'], $bd_format)
  33. );
  34. if (!empty($event['finish'])) {
  35. $event_end = L10n::getDay(
  36. !empty($event['adjust']) ?
  37. DateTimeFormat::local($event['finish'], $bd_format) : DateTimeFormat::utc($event['finish'], $bd_format)
  38. );
  39. } else {
  40. $event_end = '';
  41. }
  42. if ($simple) {
  43. if (!empty($event['summary'])) {
  44. $o = "<h3>" . BBCode::convert($event['summary'], false, $simple) . "</h3>";
  45. }
  46. if (!empty($event['desc'])) {
  47. $o .= "<div>" . BBCode::convert($event['desc'], false, $simple) . "</div>";
  48. }
  49. $o .= "<h4>" . L10n::t('Starts:') . "</h4><p>" . $event_start . "</p>";
  50. if (!$event['nofinish']) {
  51. $o .= "<h4>" . L10n::t('Finishes:') . "</h4><p>" . $event_end . "</p>";
  52. }
  53. if (!empty($event['location'])) {
  54. $o .= "<h4>" . L10n::t('Location:') . "</h4><p>" . BBCode::convert($event['location'], false, $simple) . "</p>";
  55. }
  56. return $o;
  57. }
  58. $o = '<div class="vevent">' . "\r\n";
  59. $o .= '<div class="summary event-summary">' . BBCode::convert($event['summary'], false, $simple) . '</div>' . "\r\n";
  60. $o .= '<div class="event-start"><span class="event-label">' . L10n::t('Starts:') . '</span>&nbsp;<span class="dtstart" title="'
  61. . DateTimeFormat::utc($event['start'], (!empty($event['adjust']) ? DateTimeFormat::ATOM : 'Y-m-d\TH:i:s'))
  62. . '" >' . $event_start
  63. . '</span></div>' . "\r\n";
  64. if (!$event['nofinish']) {
  65. $o .= '<div class="event-end" ><span class="event-label">' . L10n::t('Finishes:') . '</span>&nbsp;<span class="dtend" title="'
  66. . DateTimeFormat::utc($event['finish'], (!empty($event['adjust']) ? DateTimeFormat::ATOM : 'Y-m-d\TH:i:s'))
  67. . '" >' . $event_end
  68. . '</span></div>' . "\r\n";
  69. }
  70. if (!empty($event['desc'])) {
  71. $o .= '<div class="description event-description">' . BBCode::convert($event['desc'], false, $simple) . '</div>' . "\r\n";
  72. }
  73. if (!empty($event['location'])) {
  74. $o .= '<div class="event-location"><span class="event-label">' . L10n::t('Location:') . '</span>&nbsp;<span class="location">'
  75. . BBCode::convert($event['location'], false, $simple)
  76. . '</span></div>' . "\r\n";
  77. // Include a map of the location if the [map] BBCode is used.
  78. if (strpos($event['location'], "[map") !== false) {
  79. $map = Map::byLocation($event['location'], $simple);
  80. if ($map !== $event['location']) {
  81. $o .= $map;
  82. }
  83. }
  84. }
  85. $o .= '</div>' . "\r\n";
  86. return $o;
  87. }
  88. /**
  89. * @brief Convert an array with event data to bbcode.
  90. *
  91. * @param array $event Array which contains the event data.
  92. * @return string The event as a bbcode formatted string.
  93. */
  94. private static function getBBCode(array $event)
  95. {
  96. $o = '';
  97. if ($event['summary']) {
  98. $o .= '[event-summary]' . $event['summary'] . '[/event-summary]';
  99. }
  100. if ($event['desc']) {
  101. $o .= '[event-description]' . $event['desc'] . '[/event-description]';
  102. }
  103. if ($event['start']) {
  104. $o .= '[event-start]' . $event['start'] . '[/event-start]';
  105. }
  106. if (($event['finish']) && (!$event['nofinish'])) {
  107. $o .= '[event-finish]' . $event['finish'] . '[/event-finish]';
  108. }
  109. if ($event['location']) {
  110. $o .= '[event-location]' . $event['location'] . '[/event-location]';
  111. }
  112. if ($event['adjust']) {
  113. $o .= '[event-adjust]' . $event['adjust'] . '[/event-adjust]';
  114. }
  115. return $o;
  116. }
  117. /**
  118. * @brief Extract bbcode formatted event data from a string.
  119. *
  120. * @params: string $s The string which should be parsed for event data.
  121. * @return array The array with the event information.
  122. */
  123. public static function fromBBCode($text)
  124. {
  125. $ev = [];
  126. $match = '';
  127. if (preg_match("/\[event\-summary\](.*?)\[\/event\-summary\]/is", $text, $match)) {
  128. $ev['summary'] = $match[1];
  129. }
  130. $match = '';
  131. if (preg_match("/\[event\-description\](.*?)\[\/event\-description\]/is", $text, $match)) {
  132. $ev['desc'] = $match[1];
  133. }
  134. $match = '';
  135. if (preg_match("/\[event\-start\](.*?)\[\/event\-start\]/is", $text, $match)) {
  136. $ev['start'] = $match[1];
  137. }
  138. $match = '';
  139. if (preg_match("/\[event\-finish\](.*?)\[\/event\-finish\]/is", $text, $match)) {
  140. $ev['finish'] = $match[1];
  141. }
  142. $match = '';
  143. if (preg_match("/\[event\-location\](.*?)\[\/event\-location\]/is", $text, $match)) {
  144. $ev['location'] = $match[1];
  145. }
  146. $match = '';
  147. if (preg_match("/\[event\-adjust\](.*?)\[\/event\-adjust\]/is", $text, $match)) {
  148. $ev['adjust'] = $match[1];
  149. }
  150. $ev['nofinish'] = !empty($ev['start']) && empty($ev['finish']) ? 1 : 0;
  151. return $ev;
  152. }
  153. public static function sortByDate($event_list)
  154. {
  155. usort($event_list, ['self', 'compareDatesCallback']);
  156. return $event_list;
  157. }
  158. private static function compareDatesCallback($event_a, $event_b)
  159. {
  160. $date_a = (($event_a['adjust']) ? DateTimeFormat::local($event_a['start']) : $event_a['start']);
  161. $date_b = (($event_b['adjust']) ? DateTimeFormat::local($event_b['start']) : $event_b['start']);
  162. if ($date_a === $date_b) {
  163. return strcasecmp($event_a['desc'], $event_b['desc']);
  164. }
  165. return strcmp($date_a, $date_b);
  166. }
  167. /**
  168. * @brief Delete an event from the event table.
  169. *
  170. * Note: This function does only delete the event from the event table not its
  171. * related entry in the item table.
  172. *
  173. * @param int $event_id Event ID.
  174. * @return void
  175. */
  176. public static function delete($event_id)
  177. {
  178. if ($event_id == 0) {
  179. return;
  180. }
  181. DBA::delete('event', ['id' => $event_id]);
  182. Logger::log("Deleted event ".$event_id, Logger::DEBUG);
  183. }
  184. /**
  185. * @brief Store the event.
  186. *
  187. * Store the event in the event table and create an event item in the item table.
  188. *
  189. * @param array $arr Array with event data.
  190. * @return int The new event id.
  191. */
  192. public static function store($arr)
  193. {
  194. $a = self::getApp();
  195. $event = [];
  196. $event['id'] = intval(defaults($arr, 'id' , 0));
  197. $event['uid'] = intval(defaults($arr, 'uid' , 0));
  198. $event['cid'] = intval(defaults($arr, 'cid' , 0));
  199. $event['uri'] = defaults($arr, 'uri' , Item::newURI($event['uid']));
  200. $event['type'] = defaults($arr, 'type' , 'event');
  201. $event['summary'] = defaults($arr, 'summary' , '');
  202. $event['desc'] = defaults($arr, 'desc' , '');
  203. $event['location'] = defaults($arr, 'location' , '');
  204. $event['allow_cid'] = defaults($arr, 'allow_cid', '');
  205. $event['allow_gid'] = defaults($arr, 'allow_gid', '');
  206. $event['deny_cid'] = defaults($arr, 'deny_cid' , '');
  207. $event['deny_gid'] = defaults($arr, 'deny_gid' , '');
  208. $event['adjust'] = intval(defaults($arr, 'adjust' , 0));
  209. $event['nofinish'] = intval(defaults($arr, 'nofinish' , !empty($event['start']) && empty($event['finish'])));
  210. $event['created'] = DateTimeFormat::utc(defaults($arr, 'created' , 'now'));
  211. $event['edited'] = DateTimeFormat::utc(defaults($arr, 'edited' , 'now'));
  212. $event['start'] = DateTimeFormat::utc(defaults($arr, 'start' , DBA::NULL_DATETIME));
  213. $event['finish'] = DateTimeFormat::utc(defaults($arr, 'finish' , DBA::NULL_DATETIME));
  214. if ($event['finish'] < DBA::NULL_DATETIME) {
  215. $event['finish'] = DBA::NULL_DATETIME;
  216. }
  217. $private = intval(defaults($arr, 'private', 0));
  218. $conditions = ['uid' => $event['uid']];
  219. if ($event['cid']) {
  220. $conditions['id'] = $event['cid'];
  221. } else {
  222. $conditions['self'] = true;
  223. }
  224. $contact = DBA::selectFirst('contact', [], $conditions);
  225. // Existing event being modified.
  226. if ($event['id']) {
  227. // has the event actually changed?
  228. $existing_event = DBA::selectFirst('event', ['edited'], ['id' => $event['id'], 'uid' => $event['uid']]);
  229. if (!DBA::isResult($existing_event) || ($existing_event['edited'] === $event['edited'])) {
  230. $item = Item::selectFirst(['id'], ['event-id' => $event['id'], 'uid' => $event['uid']]);
  231. return DBA::isResult($item) ? $item['id'] : 0;
  232. }
  233. $updated_fields = [
  234. 'edited' => $event['edited'],
  235. 'start' => $event['start'],
  236. 'finish' => $event['finish'],
  237. 'summary' => $event['summary'],
  238. 'desc' => $event['desc'],
  239. 'location' => $event['location'],
  240. 'type' => $event['type'],
  241. 'adjust' => $event['adjust'],
  242. 'nofinish' => $event['nofinish'],
  243. ];
  244. DBA::update('event', $updated_fields, ['id' => $event['id'], 'uid' => $event['uid']]);
  245. $item = Item::selectFirst(['id'], ['event-id' => $event['id'], 'uid' => $event['uid']]);
  246. if (DBA::isResult($item)) {
  247. $object = '<object><type>' . XML::escape(ACTIVITY_OBJ_EVENT) . '</type><title></title><id>' . XML::escape($event['uri']) . '</id>';
  248. $object .= '<content>' . XML::escape(self::getBBCode($event)) . '</content>';
  249. $object .= '</object>' . "\n";
  250. $fields = ['body' => self::getBBCode($event), 'object' => $object, 'edited' => $event['edited']];
  251. Item::update($fields, ['id' => $item['id']]);
  252. $item_id = $item['id'];
  253. } else {
  254. $item_id = 0;
  255. }
  256. Addon::callHooks('event_updated', $event['id']);
  257. } else {
  258. $event['guid'] = defaults($arr, 'guid', System::createUUID());
  259. // New event. Store it.
  260. DBA::insert('event', $event);
  261. $item_id = 0;
  262. // Don't create an item for birthday events
  263. if ($event['type'] == 'event') {
  264. $event['id'] = DBA::lastInsertId();
  265. $item_arr = [];
  266. $item_arr['uid'] = $event['uid'];
  267. $item_arr['contact-id'] = $event['cid'];
  268. $item_arr['uri'] = $event['uri'];
  269. $item_arr['parent-uri'] = $event['uri'];
  270. $item_arr['guid'] = $event['guid'];
  271. $item_arr['plink'] = defaults($arr, 'plink', '');
  272. $item_arr['post-type'] = Item::PT_EVENT;
  273. $item_arr['wall'] = $event['cid'] ? 0 : 1;
  274. $item_arr['contact-id'] = $contact['id'];
  275. $item_arr['owner-name'] = $contact['name'];
  276. $item_arr['owner-link'] = $contact['url'];
  277. $item_arr['owner-avatar'] = $contact['thumb'];
  278. $item_arr['author-name'] = $contact['name'];
  279. $item_arr['author-link'] = $contact['url'];
  280. $item_arr['author-avatar'] = $contact['thumb'];
  281. $item_arr['title'] = '';
  282. $item_arr['allow_cid'] = $event['allow_cid'];
  283. $item_arr['allow_gid'] = $event['allow_gid'];
  284. $item_arr['deny_cid'] = $event['deny_cid'];
  285. $item_arr['deny_gid'] = $event['deny_gid'];
  286. $item_arr['private'] = $private;
  287. $item_arr['visible'] = 1;
  288. $item_arr['verb'] = ACTIVITY_POST;
  289. $item_arr['object-type'] = ACTIVITY_OBJ_EVENT;
  290. $item_arr['origin'] = $event['cid'] === 0 ? 1 : 0;
  291. $item_arr['body'] = self::getBBCode($event);
  292. $item_arr['event-id'] = $event['id'];
  293. $item_arr['object'] = '<object><type>' . XML::escape(ACTIVITY_OBJ_EVENT) . '</type><title></title><id>' . XML::escape($event['uri']) . '</id>';
  294. $item_arr['object'] .= '<content>' . XML::escape(self::getBBCode($event)) . '</content>';
  295. $item_arr['object'] .= '</object>' . "\n";
  296. $item_id = Item::insert($item_arr);
  297. }
  298. Addon::callHooks("event_created", $event['id']);
  299. }
  300. return $item_id;
  301. }
  302. /**
  303. * @brief Create an array with translation strings used for events.
  304. *
  305. * @return array Array with translations strings.
  306. */
  307. public static function getStrings()
  308. {
  309. // First day of the week (0 = Sunday).
  310. $firstDay = PConfig::get(local_user(), 'system', 'first_day_of_week', 0);
  311. $i18n = [
  312. "firstDay" => $firstDay,
  313. "allday" => L10n::t("all-day"),
  314. "Sun" => L10n::t("Sun"),
  315. "Mon" => L10n::t("Mon"),
  316. "Tue" => L10n::t("Tue"),
  317. "Wed" => L10n::t("Wed"),
  318. "Thu" => L10n::t("Thu"),
  319. "Fri" => L10n::t("Fri"),
  320. "Sat" => L10n::t("Sat"),
  321. "Sunday" => L10n::t("Sunday"),
  322. "Monday" => L10n::t("Monday"),
  323. "Tuesday" => L10n::t("Tuesday"),
  324. "Wednesday" => L10n::t("Wednesday"),
  325. "Thursday" => L10n::t("Thursday"),
  326. "Friday" => L10n::t("Friday"),
  327. "Saturday" => L10n::t("Saturday"),
  328. "Jan" => L10n::t("Jan"),
  329. "Feb" => L10n::t("Feb"),
  330. "Mar" => L10n::t("Mar"),
  331. "Apr" => L10n::t("Apr"),
  332. "May" => L10n::t("May"),
  333. "Jun" => L10n::t("Jun"),
  334. "Jul" => L10n::t("Jul"),
  335. "Aug" => L10n::t("Aug"),
  336. "Sep" => L10n::t("Sept"),
  337. "Oct" => L10n::t("Oct"),
  338. "Nov" => L10n::t("Nov"),
  339. "Dec" => L10n::t("Dec"),
  340. "January" => L10n::t("January"),
  341. "February" => L10n::t("February"),
  342. "March" => L10n::t("March"),
  343. "April" => L10n::t("April"),
  344. "May" => L10n::t("May"),
  345. "June" => L10n::t("June"),
  346. "July" => L10n::t("July"),
  347. "August" => L10n::t("August"),
  348. "September" => L10n::t("September"),
  349. "October" => L10n::t("October"),
  350. "November" => L10n::t("November"),
  351. "December" => L10n::t("December"),
  352. "today" => L10n::t("today"),
  353. "month" => L10n::t("month"),
  354. "week" => L10n::t("week"),
  355. "day" => L10n::t("day"),
  356. "noevent" => L10n::t("No events to display"),
  357. "dtstart_label" => L10n::t("Starts:"),
  358. "dtend_label" => L10n::t("Finishes:"),
  359. "location_label" => L10n::t("Location:")
  360. ];
  361. return $i18n;
  362. }
  363. /**
  364. * @brief Removes duplicated birthday events.
  365. *
  366. * @param array $dates Array of possibly duplicated events.
  367. * @return array Cleaned events.
  368. *
  369. * @todo We should replace this with a separate update function if there is some time left.
  370. */
  371. private static function removeDuplicates(array $dates)
  372. {
  373. $dates2 = [];
  374. foreach ($dates as $date) {
  375. if ($date['type'] == 'birthday') {
  376. $dates2[$date['uid'] . "-" . $date['cid'] . "-" . $date['start']] = $date;
  377. } else {
  378. $dates2[] = $date;
  379. }
  380. }
  381. return array_values($dates2);
  382. }
  383. /**
  384. * @brief Get an event by its event ID.
  385. *
  386. * @param int $owner_uid The User ID of the owner of the event
  387. * @param int $event_id The ID of the event in the event table
  388. * @param string $sql_extra
  389. * @return array Query result
  390. */
  391. public static function getListById($owner_uid, $event_id, $sql_extra = '')
  392. {
  393. $return = [];
  394. // Ownly allow events if there is a valid owner_id.
  395. if ($owner_uid == 0) {
  396. return $return;
  397. }
  398. // Query for the event by event id
  399. $r = q("SELECT `event`.*, `item`.`id` AS `itemid` FROM `event`
  400. LEFT JOIN `item` ON `item`.`event-id` = `event`.`id` AND `item`.`uid` = `event`.`uid`
  401. WHERE `event`.`uid` = %d AND `event`.`id` = %d $sql_extra",
  402. intval($owner_uid),
  403. intval($event_id)
  404. );
  405. if (DBA::isResult($r)) {
  406. $return = self::removeDuplicates($r);
  407. }
  408. return $return;
  409. }
  410. /**
  411. * @brief Get all events in a specific time frame.
  412. *
  413. * @param int $owner_uid The User ID of the owner of the events.
  414. * @param array $event_params An associative array with
  415. * int 'ignore' =>
  416. * string 'start' => Start time of the timeframe.
  417. * string 'finish' => Finish time of the timeframe.
  418. * string 'adjust_start' =>
  419. * string 'adjust_finish' =>
  420. *
  421. * @param string $sql_extra Additional sql conditions (e.g. permission request).
  422. *
  423. * @return array Query results.
  424. */
  425. public static function getListByDate($owner_uid, $event_params, $sql_extra = '')
  426. {
  427. $return = [];
  428. // Only allow events if there is a valid owner_id.
  429. if ($owner_uid == 0) {
  430. return $return;
  431. }
  432. // Query for the event by date.
  433. // @todo Slow query (518 seconds to run), to be optimzed
  434. $r = q("SELECT `event`.*, `item`.`id` AS `itemid` FROM `event`
  435. LEFT JOIN `item` ON `item`.`event-id` = `event`.`id` AND `item`.`uid` = `event`.`uid`
  436. WHERE `event`.`uid` = %d AND event.ignore = %d
  437. AND ((`adjust` = 0 AND (`finish` >= '%s' OR (nofinish AND start >= '%s')) AND `start` <= '%s')
  438. OR (`adjust` = 1 AND (`finish` >= '%s' OR (nofinish AND start >= '%s')) AND `start` <= '%s'))
  439. $sql_extra ",
  440. intval($owner_uid),
  441. intval($event_params["ignore"]),
  442. DBA::escape($event_params["start"]),
  443. DBA::escape($event_params["start"]),
  444. DBA::escape($event_params["finish"]),
  445. DBA::escape($event_params["adjust_start"]),
  446. DBA::escape($event_params["adjust_start"]),
  447. DBA::escape($event_params["adjust_finish"])
  448. );
  449. if (DBA::isResult($r)) {
  450. $return = self::removeDuplicates($r);
  451. }
  452. return $return;
  453. }
  454. /**
  455. * @brief Convert an array query results in an array which could be used by the events template.
  456. *
  457. * @param array $event_result Event query array.
  458. * @return array Event array for the template.
  459. */
  460. public static function prepareListForTemplate(array $event_result)
  461. {
  462. $event_list = [];
  463. $last_date = '';
  464. $fmt = L10n::t('l, F j');
  465. foreach ($event_result as $event) {
  466. $item = Item::selectFirst(['plink', 'author-name', 'author-avatar', 'author-link'], ['id' => $event['itemid']]);
  467. if (!DBA::isResult($item)) {
  468. // Using default values when no item had been found
  469. $item = ['plink' => '', 'author-name' => '', 'author-avatar' => '', 'author-link' => ''];
  470. }
  471. $event = array_merge($event, $item);
  472. $start = $event['adjust'] ? DateTimeFormat::local($event['start'], 'c') : DateTimeFormat::utc($event['start'], 'c');
  473. $j = $event['adjust'] ? DateTimeFormat::local($event['start'], 'j') : DateTimeFormat::utc($event['start'], 'j');
  474. $day = $event['adjust'] ? DateTimeFormat::local($event['start'], $fmt) : DateTimeFormat::utc($event['start'], $fmt);
  475. $day = L10n::getDay($day);
  476. if ($event['nofinish']) {
  477. $end = null;
  478. } else {
  479. $end = $event['adjust'] ? DateTimeFormat::local($event['finish'], 'c') : DateTimeFormat::utc($event['finish'], 'c');
  480. }
  481. $is_first = ($day !== $last_date);
  482. $last_date = $day;
  483. // Show edit and drop actions only if the user is the owner of the event and the event
  484. // is a real event (no bithdays).
  485. $edit = null;
  486. $copy = null;
  487. $drop = null;
  488. if (local_user() && local_user() == $event['uid'] && $event['type'] == 'event') {
  489. $edit = !$event['cid'] ? [System::baseUrl() . '/events/event/' . $event['id'], L10n::t('Edit event') , '', ''] : null;
  490. $copy = !$event['cid'] ? [System::baseUrl() . '/events/copy/' . $event['id'] , L10n::t('Duplicate event'), '', ''] : null;
  491. $drop = [System::baseUrl() . '/events/drop/' . $event['id'] , L10n::t('Delete event') , '', ''];
  492. }
  493. $title = strip_tags(html_entity_decode(BBCode::convert($event['summary']), ENT_QUOTES, 'UTF-8'));
  494. if (!$title) {
  495. list($title, $_trash) = explode("<br", BBCode::convert($event['desc']), 2);
  496. $title = strip_tags(html_entity_decode($title, ENT_QUOTES, 'UTF-8'));
  497. }
  498. $author_link = $event['author-link'];
  499. $plink = $event['plink'];
  500. $event['author-link'] = Contact::magicLink($author_link);
  501. $event['plink'] = Contact::magicLink($author_link, $plink);
  502. $html = self::getHTML($event);
  503. $event['desc'] = BBCode::convert($event['desc']);
  504. $event['location'] = BBCode::convert($event['location']);
  505. $event_list[] = [
  506. 'id' => $event['id'],
  507. 'start' => $start,
  508. 'end' => $end,
  509. 'allDay' => false,
  510. 'title' => $title,
  511. 'j' => $j,
  512. 'd' => $day,
  513. 'edit' => $edit,
  514. 'drop' => $drop,
  515. 'copy' => $copy,
  516. 'is_first' => $is_first,
  517. 'item' => $event,
  518. 'html' => $html,
  519. 'plink' => [$event['plink'], L10n::t('link to source'), '', ''],
  520. ];
  521. }
  522. return $event_list;
  523. }
  524. /**
  525. * @brief Format event to export format (ical/csv).
  526. *
  527. * @param array $events Query result for events.
  528. * @param string $format The output format (ical/csv).
  529. * @param string $timezone The timezone of the user (not implemented yet).
  530. *
  531. * @return string Content according to selected export format.
  532. *
  533. * @todo Implement timezone support
  534. */
  535. private static function formatListForExport(array $events, $format, $timezone)
  536. {
  537. if (!count($events)) {
  538. return '';
  539. }
  540. switch ($format) {
  541. // Format the exported data as a CSV file.
  542. case "csv":
  543. header("Content-type: text/csv");
  544. $o = '"Subject", "Start Date", "Start Time", "Description", "End Date", "End Time", "Location"' . PHP_EOL;
  545. foreach ($events as $event) {
  546. /// @todo The time / date entries don't include any information about the
  547. /// timezone the event is scheduled in :-/
  548. $tmp1 = strtotime($event['start']);
  549. $tmp2 = strtotime($event['finish']);
  550. $time_format = "%H:%M:%S";
  551. $date_format = "%Y-%m-%d";
  552. $o .= '"' . $event['summary'] . '", "' . strftime($date_format, $tmp1) .
  553. '", "' . strftime($time_format, $tmp1) . '", "' . $event['desc'] .
  554. '", "' . strftime($date_format, $tmp2) .
  555. '", "' . strftime($time_format, $tmp2) .
  556. '", "' . $event['location'] . '"' . PHP_EOL;
  557. }
  558. break;
  559. // Format the exported data as a ics file.
  560. case "ical":
  561. header("Content-type: text/ics");
  562. $o = 'BEGIN:VCALENDAR' . PHP_EOL
  563. . 'VERSION:2.0' . PHP_EOL
  564. . 'PRODID:-//friendica calendar export//0.1//EN' . PHP_EOL;
  565. /// @todo include timezone informations in cases were the time is not in UTC
  566. // see http://tools.ietf.org/html/rfc2445#section-4.8.3
  567. // . 'BEGIN:VTIMEZONE' . PHP_EOL
  568. // . 'TZID:' . $timezone . PHP_EOL
  569. // . 'END:VTIMEZONE' . PHP_EOL;
  570. // TODO instead of PHP_EOL CRLF should be used for long entries
  571. // but test your solution against http://icalvalid.cloudapp.net/
  572. // also long lines SHOULD be split at 75 characters length
  573. foreach ($events as $event) {
  574. if ($event['adjust'] == 1) {
  575. $UTC = 'Z';
  576. } else {
  577. $UTC = '';
  578. }
  579. $o .= 'BEGIN:VEVENT' . PHP_EOL;
  580. if ($event['start']) {
  581. $tmp = strtotime($event['start']);
  582. $dtformat = "%Y%m%dT%H%M%S" . $UTC;
  583. $o .= 'DTSTART:' . strftime($dtformat, $tmp) . PHP_EOL;
  584. }
  585. if (!$event['nofinish']) {
  586. $tmp = strtotime($event['finish']);
  587. $dtformat = "%Y%m%dT%H%M%S" . $UTC;
  588. $o .= 'DTEND:' . strftime($dtformat, $tmp) . PHP_EOL;
  589. }
  590. if ($event['summary']) {
  591. $tmp = $event['summary'];
  592. $tmp = str_replace(PHP_EOL, PHP_EOL . ' ', $tmp);
  593. $tmp = addcslashes($tmp, ',;');
  594. $o .= 'SUMMARY:' . $tmp . PHP_EOL;
  595. }
  596. if ($event['desc']) {
  597. $tmp = $event['desc'];
  598. $tmp = str_replace(PHP_EOL, PHP_EOL . ' ', $tmp);
  599. $tmp = addcslashes($tmp, ',;');
  600. $o .= 'DESCRIPTION:' . $tmp . PHP_EOL;
  601. }
  602. if ($event['location']) {
  603. $tmp = $event['location'];
  604. $tmp = str_replace(PHP_EOL, PHP_EOL . ' ', $tmp);
  605. $tmp = addcslashes($tmp, ',;');
  606. $o .= 'LOCATION:' . $tmp . PHP_EOL;
  607. }
  608. $o .= 'END:VEVENT' . PHP_EOL;
  609. $o .= PHP_EOL;
  610. }
  611. $o .= 'END:VCALENDAR' . PHP_EOL;
  612. break;
  613. }
  614. return $o;
  615. }
  616. /**
  617. * @brief Get all events for a user ID.
  618. *
  619. * The query for events is done permission sensitive.
  620. * If the user is the owner of the calendar they
  621. * will get all of their available events.
  622. * If the user is only a visitor only the public events will
  623. * be available.
  624. *
  625. * @param int $uid The user ID.
  626. *
  627. * @return array Query results.
  628. */
  629. private static function getListByUserId($uid = 0)
  630. {
  631. $return = [];
  632. if ($uid == 0) {
  633. return $return;
  634. }
  635. $fields = ['start', 'finish', 'adjust', 'summary', 'desc', 'location', 'nofinish'];
  636. $conditions = ['uid' => $uid, 'cid' => 0];
  637. // Does the user who requests happen to be the owner of the events
  638. // requested? then show all of your events, otherwise only those that
  639. // don't have limitations set in allow_cid and allow_gid.
  640. if (local_user() != $uid) {
  641. $conditions += ['allow_cid' => '', 'allow_gid' => ''];
  642. }
  643. $events = DBA::select('event', $fields, $conditions);
  644. if (DBA::isResult($events)) {
  645. $return = DBA::toArray($events);
  646. }
  647. return $return;
  648. }
  649. /**
  650. *
  651. * @param int $uid The user ID.
  652. * @param string $format Output format (ical/csv).
  653. * @return array With the results:
  654. * bool 'success' => True if the processing was successful,<br>
  655. * string 'format' => The output format,<br>
  656. * string 'extension' => The file extension of the output format,<br>
  657. * string 'content' => The formatted output content.<br>
  658. *
  659. * @todo Respect authenticated users with events_by_uid().
  660. */
  661. public static function exportListByUserId($uid, $format = 'ical')
  662. {
  663. $process = false;
  664. $user = DBA::selectFirst('user', ['timezone'], ['uid' => $uid]);
  665. if (DBA::isResult($user)) {
  666. $timezone = $user['timezone'];
  667. }
  668. // Get all events which are owned by a uid (respects permissions).
  669. $events = self::getListByUserId($uid);
  670. // We have the events that are available for the requestor.
  671. // Now format the output according to the requested format.
  672. $res = self::formatListForExport($events, $format, $timezone);
  673. // If there are results the precess was successfull.
  674. if (!empty($res)) {
  675. $process = true;
  676. }
  677. // Get the file extension for the format.
  678. switch ($format) {
  679. case "ical":
  680. $file_ext = "ics";
  681. break;
  682. case "csv":
  683. $file_ext = "csv";
  684. break;
  685. default:
  686. $file_ext = "";
  687. }
  688. $return = [
  689. 'success' => $process,
  690. 'format' => $format,
  691. 'extension' => $file_ext,
  692. 'content' => $res,
  693. ];
  694. return $return;
  695. }
  696. /**
  697. * @brief Format an item array with event data to HTML.
  698. *
  699. * @param array $item Array with item and event data.
  700. * @return string HTML output.
  701. */
  702. public static function getItemHTML(array $item) {
  703. $same_date = false;
  704. $finish = false;
  705. // Set the different time formats.
  706. $dformat = L10n::t('l F d, Y \@ g:i A'); // Friday January 18, 2011 @ 8:01 AM.
  707. $dformat_short = L10n::t('D g:i A'); // Fri 8:01 AM.
  708. $tformat = L10n::t('g:i A'); // 8:01 AM.
  709. // Convert the time to different formats.
  710. $dtstart_dt = L10n::getDay(
  711. $item['event-adjust'] ?
  712. DateTimeFormat::local($item['event-start'], $dformat)
  713. : DateTimeFormat::utc($item['event-start'], $dformat)
  714. );
  715. $dtstart_title = DateTimeFormat::utc($item['event-start'], $item['event-adjust'] ? DateTimeFormat::ATOM : 'Y-m-d\TH:i:s');
  716. // Format: Jan till Dec.
  717. $month_short = L10n::getDayShort(
  718. $item['event-adjust'] ?
  719. DateTimeFormat::local($item['event-start'], 'M')
  720. : DateTimeFormat::utc($item['event-start'], 'M')
  721. );
  722. // Format: 1 till 31.
  723. $date_short = $item['event-adjust'] ?
  724. DateTimeFormat::local($item['event-start'], 'j')
  725. : DateTimeFormat::utc($item['event-start'], 'j');
  726. $start_time = $item['event-adjust'] ?
  727. DateTimeFormat::local($item['event-start'], $tformat)
  728. : DateTimeFormat::utc($item['event-start'], $tformat);
  729. $start_short = L10n::getDayShort(
  730. $item['event-adjust'] ?
  731. DateTimeFormat::local($item['event-start'], $dformat_short)
  732. : DateTimeFormat::utc($item['event-start'], $dformat_short)
  733. );
  734. // If the option 'nofinisch' isn't set, we need to format the finish date/time.
  735. if (!$item['event-nofinish']) {
  736. $finish = true;
  737. $dtend_dt = L10n::getDay(
  738. $item['event-adjust'] ?
  739. DateTimeFormat::local($item['event-finish'], $dformat)
  740. : DateTimeFormat::utc($item['event-finish'], $dformat)
  741. );
  742. $dtend_title = DateTimeFormat::utc($item['event-finish'], $item['event-adjust'] ? DateTimeFormat::ATOM : 'Y-m-d\TH:i:s');
  743. $end_short = L10n::getDayShort(
  744. $item['event-adjust'] ?
  745. DateTimeFormat::local($item['event-finish'], $dformat_short)
  746. : DateTimeFormat::utc($item['event-finish'], $dformat_short)
  747. );
  748. $end_time = $item['event-adjust'] ?
  749. DateTimeFormat::local($item['event-finish'], $tformat)
  750. : DateTimeFormat::utc($item['event-finish'], $tformat);
  751. // Check if start and finish time is at the same day.
  752. if (substr($dtstart_title, 0, 10) === substr($dtend_title, 0, 10)) {
  753. $same_date = true;
  754. }
  755. } else {
  756. $dtend_title = '';
  757. $dtend_dt = '';
  758. $end_time = '';
  759. $end_short = '';
  760. }
  761. // Format the event location.
  762. $location = self::locationToArray($item['event-location']);
  763. // Construct the profile link (magic-auth).
  764. $profile_link = Contact::magicLinkById($item['author-id']);
  765. $tpl = Renderer::getMarkupTemplate('event_stream_item.tpl');
  766. $return = Renderer::replaceMacros($tpl, [
  767. '$id' => $item['event-id'],
  768. '$title' => prepare_text($item['event-summary']),
  769. '$dtstart_label' => L10n::t('Starts:'),
  770. '$dtstart_title' => $dtstart_title,
  771. '$dtstart_dt' => $dtstart_dt,
  772. '$finish' => $finish,
  773. '$dtend_label' => L10n::t('Finishes:'),
  774. '$dtend_title' => $dtend_title,
  775. '$dtend_dt' => $dtend_dt,
  776. '$month_short' => $month_short,
  777. '$date_short' => $date_short,
  778. '$same_date' => $same_date,
  779. '$start_time' => $start_time,
  780. '$start_short' => $start_short,
  781. '$end_time' => $end_time,
  782. '$end_short' => $end_short,
  783. '$author_name' => $item['author-name'],
  784. '$author_link' => $profile_link,
  785. '$author_avatar' => $item['author-avatar'],
  786. '$description' => prepare_text($item['event-desc']),
  787. '$location_label' => L10n::t('Location:'),
  788. '$show_map_label' => L10n::t('Show map'),
  789. '$hide_map_label' => L10n::t('Hide map'),
  790. '$map_btn_label' => L10n::t('Show map'),
  791. '$location' => $location
  792. ]);
  793. return $return;
  794. }
  795. /**
  796. * @brief Format a string with map bbcode to an array with location data.
  797. *
  798. * Note: The string must only contain location data. A string with no bbcode will be
  799. * handled as location name.
  800. *
  801. * @param string $s The string with the bbcode formatted location data.
  802. *
  803. * @return array The array with the location data.
  804. * 'name' => The name of the location,<br>
  805. * 'address' => The address of the location,<br>
  806. * 'coordinates' => Latitude‎ and longitude‎ (e.g. '48.864716,2.349014').<br>
  807. */
  808. private static function locationToArray($s = '') {
  809. if ($s == '') {
  810. return [];
  811. }
  812. $location = ['name' => $s];
  813. // Map tag with location name - e.g. [map]Paris[/map].
  814. if (strpos($s, '[/map]') !== false) {
  815. $found = preg_match("/\[map\](.*?)\[\/map\]/ism", $s, $match);
  816. if (intval($found) > 0 && array_key_exists(1, $match)) {
  817. $location['address'] = $match[1];
  818. // Remove the map bbcode from the location name.
  819. $location['name'] = str_replace($match[0], "", $s);
  820. }
  821. // Map tag with coordinates - e.g. [map=48.864716,2.349014].
  822. } elseif (strpos($s, '[map=') !== false) {
  823. $found = preg_match("/\[map=(.*?)\]/ism", $s, $match);
  824. if (intval($found) > 0 && array_key_exists(1, $match)) {
  825. $location['coordinates'] = $match[1];
  826. // Remove the map bbcode from the location name.
  827. $location['name'] = str_replace($match[0], "", $s);
  828. }
  829. }
  830. $location['name'] = prepare_text($location['name']);
  831. // Construct the map HTML.
  832. if (isset($location['address'])) {
  833. $location['map'] = '<div class="map">' . Map::byLocation($location['address']) . '</div>';
  834. } elseif (isset($location['coordinates'])) {
  835. $location['map'] = '<div class="map">' . Map::byCoordinates(str_replace('/', ' ', $location['coordinates'])) . '</div>';
  836. }
  837. return $location;
  838. }
  839. /**
  840. * @brief Add new birthday event for this person
  841. *
  842. * @param array $contact Contact array, expects: id, uid, url, name
  843. * @param string $birthday Birthday of the contact
  844. * @return bool
  845. */
  846. public static function createBirthday($contact, $birthday)
  847. {
  848. // Check for duplicates
  849. $condition = [
  850. 'uid' => $contact['uid'],
  851. 'cid' => $contact['id'],
  852. 'start' => DateTimeFormat::utc($birthday),
  853. 'type' => 'birthday'
  854. ];
  855. if (DBA::exists('event', $condition)) {
  856. return false;
  857. }
  858. /*
  859. * Add new birthday event for this person
  860. *
  861. * summary is just a readable placeholder in case the event is shared
  862. * with others. We will replace it during presentation to our $importer
  863. * to contain a sparkle link and perhaps a photo.
  864. */
  865. $values = [
  866. 'uid' => $contact['uid'],
  867. 'cid' => $contact['id'],
  868. 'start' => DateTimeFormat::utc($birthday),
  869. 'finish' => DateTimeFormat::utc($birthday . ' + 1 day '),
  870. 'summary' => L10n::t('%s\'s birthday', $contact['name']),
  871. 'desc' => L10n::t('Happy Birthday %s', ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'),
  872. 'type' => 'birthday',
  873. 'adjust' => 0
  874. ];
  875. self::store($values);
  876. return true;
  877. }
  878. }