Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 
 

1038 lignes
33 KiB

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