From 71c9f562ae4664beef2da6ee11c0f38d3e4eb094 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Mon, 20 Jun 2016 23:31:49 +0200 Subject: [PATCH] move the cal addon (exporting calendars) to core --- include/event.php | 219 +++++++++++++++++++++++++++++++- include/features.php | 1 + mod/cal.php | 50 ++++++++ mod/events.php | 21 +++ view/templates/events_aside.tpl | 9 ++ 5 files changed, 298 insertions(+), 2 deletions(-) create mode 100644 view/templates/events_aside.tpl diff --git a/include/event.php b/include/event.php index df9b95857..befda64eb 100644 --- a/include/event.php +++ b/include/event.php @@ -10,8 +10,6 @@ require_once('include/datetime.php'); function format_event_html($ev, $simple = false) { - - if(! ((is_array($ev)) && count($ev))) return ''; @@ -614,3 +612,220 @@ function process_events ($arr) { return $events; } + +/** + * @brief Format event to export format (ical/csv) + * + * @param array $events Query result for events + * @param string $format The output format (ical/csv) + * @param string $timezone The timezone of the user (not implemented yet) + * + * @return string Content according to selected export format + */ +function event_format_export ($events, $format = 'ical', $timezone) { + if(! ((is_array($events)) && count($events))) + return; + + switch ($format) { + // format the exported data as a CSV file + case "csv": + header("Content-type: text/csv"); + $o = '"Subject", "Start Date", "Start Time", "Description", "End Date", "End Time", "Location"' . PHP_EOL; + + foreach ($events as $event) { + /// @todo the time / date entries don't include any information about the + // timezone the event is scheduled in :-/ + $tmp1 = strtotime($event['start']); + $tmp2 = strtotime($event['finish']); + $time_format = "%H:%M:%S"; + $date_format = "%Y-%m-%d"; + $o .= '"'.$event['summary'].'", "'.strftime($date_format, $tmp1) . + '", "'.strftime($time_format, $tmp1).'", "'.$event['desc'] . + '", "'.strftime($date_format, $tmp2) . + '", "'.strftime($time_format, $tmp2) . + '", "'.$event['location'].'"' . PHP_EOL; + } + break; + + // format the exported data as a ics file + case "ical": + header("Content-type: text/ics"); + $o = 'BEGIN:VCALENDAR'. PHP_EOL + . 'VERSION:2.0' . PHP_EOL + . 'PRODID:-//friendica calendar export//0.1//EN' . PHP_EOL; + /// @todo include timezone informations in cases were the time is not in UTC + // see http://tools.ietf.org/html/rfc2445#section-4.8.3 + // . 'BEGIN:VTIMEZONE' . PHP_EOL + // . 'TZID:' . $timezone . PHP_EOL + // . 'END:VTIMEZONE' . PHP_EOL; + // TODO instead of PHP_EOL CRLF should be used for long entries + // but test your solution against http://icalvalid.cloudapp.net/ + // also long lines SHOULD be split at 75 characters length + foreach ($events as $event) { + if ($event['adjust'] == 1) { + $UTC = 'Z'; + } else { + $UTC = ''; + } + $o .= 'BEGIN:VEVENT' . PHP_EOL; + if ($event[start]) { + $tmp = strtotime($event['start']); + $dtformat = "%Y%m%dT%H%M%S".$UTC; + $o .= 'DTSTART:'.strftime($dtformat, $tmp).PHP_EOL; + } + if ($event['finish']) { + $tmp = strtotime($event['finish']); + $dtformat = "%Y%m%dT%H%M%S".$UTC; + $o .= 'DTEND:'.strftime($dtformat, $tmp).PHP_EOL; + } + if ($event['summary']) + $tmp = $event['summary']; + $tmp = str_replace(PHP_EOL, PHP_EOL.' ',$tmp); + $tmp = addcslashes($tmp, ',;'); + $o .= 'SUMMARY:' . $tmp . PHP_EOL; + if ($event['desc']) + $tmp = $event['desc']; + $tmp = str_replace(PHP_EOL, PHP_EOL.' ',$tmp); + $tmp = addcslashes($tmp, ',;'); + $o .= 'DESCRIPTION:' . $tmp . PHP_EOL; + if ($event['location']) { + $tmp = $event['location']; + $tmp = str_replace(PHP_EOL, PHP_EOL.' ',$tmp); + $tmp = addcslashes($tmp, ',;'); + $o .= 'LOCATION:' . $tmp . PHP_EOL; + } + + $o .= 'END:VEVENT' . PHP_EOL; + $o .= PHP_EOL; + } + + $o .= 'END:VCALENDAR' . PHP_EOL; + break; + } + + return $o; +} + +/** + * @brief Get all events for a user ID + * + * The query for events is done permission sensitive + * If the user is the owner of the calendar he/she + * will get all of his/her available events. + * If the user is only a visitor only the public events will + * be available + * + * @param int $uid The user ID + * @param int $sql_extra Additional sql conditions for permission + * + * @return array Query results + */ +function events_by_uid($uid = 0, $sql_extra = '') { + if($uid == 0) + return; + + // The permission condition if no condition was transmitted + if($sql_extra == '') + $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' "; + + // does the user who requests happen to be the owner of the events + // requested? then show all of your events, otherwise only those that + // don't have limitations set in allow_cid and allow_gid + if (local_user() == $uid) { + $r = q("SELECT `start`, `finish`, `adjust`, `summary`, `desc`, `location` + FROM `event` WHERE `uid`= %d AND `cid` = 0 ", + intval($uid) + ); + } else { + $r = q("SELECT `start`, `finish`, `adjust`, `summary`, `desc`, `location`FROM `event` + WHERE `uid` = %d AND `cid` = 0 $sql_extra ", + intval($uid) + ); + } + + if(count($r)) + return $r; +} + +/** + * + * @param int $uid The user ID + * @param string $format Output format (ical/csv) + * @return array With the results + * bool 'success' => True if the processing was successful + * string 'format' => The output format + * string 'extension' => The file extension of the output format + * string 'content' => The formatted output content + * + * @todo Respect authenticated users with events_by_uid() + */ +function event_export($uid, $format = 'ical') { + + $process = false; + + // we are allowed to show events + // get the timezone the user is in + $r = q("SELECT `timezone` FROM `user` WHERE `uid` = %d LIMIT 1", intval($uid)); + if (count($r)) + $timezone = $r[0]['timezone']; + + // get all events which are owned by a uid (respects permissions); + $events = events_by_uid($uid); + + // we have the events that are available for the requestor + // now format the output according to the requested format + if(count($events)) + $res = event_format_export($events, $format, $timezone); + + // If there are results the precess was successfull + if(x($res)) + $process = true; + + // get the file extension for the format + switch ($format) { + case "ical": + $file_ext = "ics"; + break; + + case "csv": + $file_ext = "csv"; + break; + + default: + $file_ext = ""; + } + + $arr = array( + 'success' => $process, + 'format' => $format, + 'extension' => $file_ext, + 'content' => $res, + ); + + return $arr; +} + +/** + * @brief Get the events widget + * + * @return string Formated html of the evens widget + */ +function widget_events() { + $a = get_app(); + + $owner_uid = $a->data['user']['uid']; + // $a->data is only available if the profile page is visited. If the visited page is not part + // of the profile page it should be the personal /events page. So we can use $a->user + $user = ($a->data['user']['nickname'] ? $a->data['user']['nickname'] : $a->user['nickname']); + + if( !(local_user() )&& !(feature_enabled($owner_uid, "export_calendar")) ) + return; + + return replace_macros(get_markup_template("events_aside.tpl"), array( + '$etitle' => t("Export"), + '$export_ical' => t("Export calendar as ical"), + '$export_csv' => t("Export calendar as csv"), + '$user' => $user + )); + +} diff --git a/include/features.php b/include/features.php index 87a9b46d5..b3e3454b1 100644 --- a/include/features.php +++ b/include/features.php @@ -64,6 +64,7 @@ function get_features($filtered = true) { //array('expire', t('Content Expiration'), t('Remove old posts/comments after a period of time')), array('multi_profiles', t('Multiple Profiles'), t('Ability to create multiple profiles'), false, get_config('feature_lock','multi_profiles')), array('photo_location', t('Photo Location'), t('Photo metadata is normally stripped. This extracts the location (if present) prior to stripping metadata and links it to a map.'), false, get_config('feature_lock','photo_location')), + array('export_calendar', t('Export Public Calendar'), t('Ability for visitors to download the public calendar'), false, get_config('feature_lock','export_calendar')), ), // Post composition diff --git a/mod/cal.php b/mod/cal.php index 0cde2a6ec..a12a65342 100644 --- a/mod/cal.php +++ b/mod/cal.php @@ -33,6 +33,11 @@ function cal_init(&$a) { $a->data['user'] = $user[0]; $a->profile_uid = $user[0]['uid']; + // if it's a json request abort here becaus we don't + // need the widget data + if ($a->argv[2] === 'json') + return; + $profile = get_profiledata_by_nick($nick, $a->profile_uid); if((intval($profile['page-flags']) == PAGE_COMMUNITY) || (intval($profile['page-flags']) == PAGE_PRVGROUP)) @@ -50,10 +55,13 @@ function cal_init(&$a) { '$pdesc' => (($profile['pdesc'] != "") ? $profile['pdesc'] : ""), )); + $cal_widget = widget_events(); + if(! x($a->page,'aside')) $a->page['aside'] = ''; $a->page['aside'] .= $vcard_widget; + $a->page['aside'] .= $cal_widget; } return; @@ -95,6 +103,13 @@ function cal_content(&$a) { $m = 0; $ignored = ((x($_REQUEST,'ignored')) ? intval($_REQUEST['ignored']) : 0); + if($a->argc == 4) { + if($a->argv[2] == 'export') { + $mode = 'export'; + $format = $a->argv[3]; + } + } + // // Setup permissions structures // @@ -104,6 +119,7 @@ function cal_content(&$a) { $contact_id = 0; $owner_uid = $a->data['user']['uid']; + $nick = $a->data['user']['nickname']; if(is_array($_SESSION['remote'])) { foreach($_SESSION['remote'] as $v) { @@ -276,4 +292,38 @@ function cal_content(&$a) { return $o; } + + if($mode == 'export') { + if(! (intval($owner_uid))) { + notice( t('User not found')); + return; + } + + if(! (feature_enabled($owner_uid, "export_calendar"))) { + notice( t('Permission denied.') . EOL); + return; + } + + // Get the export data by uid + $evexport = event_export($owner_uid, $format); + + if ($evexport["success"] == false ) { + if($evexport["content"]) + notice( t('This calendar format is not supported') ); + else + notice( t('No exportable data found')); + + return; + } + + // If nothing went wrong we can echo the export content + if ($evexport["success"] == true ) { + header('Content-type: text/calendar'); + header('content-disposition: attachment; filename="' . t('calendar') . '-' . $nick . '.' . $evexport["extension"] . '"' ); + echo $evexport["content"]; + killme(); + } + + return; + } } diff --git a/mod/events.php b/mod/events.php index 617627ac4..b53fe9fd9 100644 --- a/mod/events.php +++ b/mod/events.php @@ -8,6 +8,27 @@ require_once('include/datetime.php'); require_once('include/event.php'); require_once('include/items.php'); +function events_init(&$a) { + if(! local_user()) + return; + + if($a->argc == 1) { + // if it's a json request abort here becaus we don't + // need the widget data + if($a->argv[1] !== 'json') + return; + + $cal_widget = widget_events(); + + if(! x($a->page,'aside')) + $a->page['aside'] = ''; + + $a->page['aside'] .= $cal_widget; + } + + return; +} + function events_post(&$a) { logger('post: ' . print_r($_REQUEST,true)); diff --git a/view/templates/events_aside.tpl b/view/templates/events_aside.tpl new file mode 100644 index 000000000..e9b54771d --- /dev/null +++ b/view/templates/events_aside.tpl @@ -0,0 +1,9 @@ + +