diff --git a/api/src/Schedule.php b/api/src/Schedule.php index c18d99e..3754ca8 100644 --- a/api/src/Schedule.php +++ b/api/src/Schedule.php @@ -21,7 +21,7 @@ private function icalFormatTime($time) { . str_pad($min, 2, '0', STR_PAD_LEFT) . "00"; } - + private function firstDayAfterDate($weekday, $startDate) { $weekdayOfStart = date('w', $startDate); if ($weekdayOfStart > $weekday) { @@ -50,59 +50,123 @@ public function generateIcal($schedule) { // Start generating code $code = ""; - // Header - $code .= "BEGIN:VCALENDAR\r\n"; - $code .= "VERSION:2.0\r\n"; - $code .= "PRODID: -//CSH ScheduleMaker//iCal4j 1.0//EN\r\n"; - $code .= "METHOD:PUBLISH\r\n"; - $code .= "CALSCALE:GREGORIAN\r\n"; - // Iterate over all the courses foreach($schedule['courses'] as $course) { // Skip classes that don't meet if(empty($course['times'])) { continue; } - - // Iterate over all the times - foreach($course['times'] as $time) { - $code .= "BEGIN:VEVENT\r\n"; - $code .= "UID:" . md5(uniqid(mt_rand(), true) . " @{$HTTPROOTADDRESS}"); - $code .= "\r\n"; - $code .= "TZID:America/New_York\r\n"; - $code .= "DTSTAMP:" . gmdate('Ymd') . "T" . gmdate("His") . "Z\r\n"; - - $startTime = $this->icalFormatTime($time['start']); - $endTime = $this->icalFormatTime($time['end']); - - // The start day of the event MUST be offset by it's day - // the -1 is b/c quarter starts are on Monday(=1) - // This /could/ be done via the RRULE WKST param, but that means - // translating days from numbers to some other esoteric format. - // @TODO: Retrieve the timezone from php or the config file - $day = date("Ymd", $this->firstDayAfterDate($time['day'], $termStart)); - - $code .= "DTSTART;TZID=America/New_York:{$day}T{$startTime}\r\n"; - $code .= "DTEND;TZID=America/New_York:{$day}T{$endTime}\r\n"; - $code .= "RRULE:FREQ=WEEKLY;UNTIL={$termEnd}\r\n"; - $code .= "ORGANIZER:RIT\r\n"; - - // Course name - $code .= "SUMMARY:{$course['title']}"; - if($course['courseNum'] != 'non') { - $code .= " ({$course['courseNum']})"; + else { + // Get all the times for this course + $times = array(); // Instantiate an array to hold the details of each time the course meets + $checker = array(); // Allows us to check if all the details are the same across each time the course meets + foreach($course['times'] as $time) { // Iterate over each time the course meets + // Add the details of the course to the $times array for use when generating the event + array_push($times, array( + "day" => (int)$time['day'], + "start" => (int)$time['start'], + "end" => (int)$time['end'], + "bldg" => $time['bldg'], + "room" => $time['room'], + "offCampus" => $time['off_campus'] + )); + // Add the details of the course to the $checker array as a single array object for use when checking if all the details are the same across each time the course meets + array_push($checker, array( + "start" => (int)$time['start'], + "end" => (int)$time['end'], + "bldg" => $time['bldg'], + "room" => $time['room'], + "offCampus" => $time['off_campus'] + )); } - $code .= "\r\n"; - // Meeting location - if($course['courseNum'] != 'non') { - $bldg = $time['bldg'][$schedule['bldgStyle']]; - $code .= "LOCATION:{$bldg}-{$time['room']}\r\n"; + /* If the details for the course are equal across each day, then set up a single series that repeats on each day the course runs. + * This compares start time, end time, building, room, and off_campus flag all as one object; see $checker array above. + * If any of the times have different data, then the count will return a number higher than 1 and the code will move to the else statement. + */ + if (count(array_unique($checker, SORT_REGULAR)) === 1) { + $code .= "BEGIN:VEVENT\r\n"; + $code .= "UID:" . md5(uniqid(mt_rand(), true) . " @{$HTTPROOTADDRESS}"); + $code .= "\r\n"; + $code .= "TZID:America/New_York\r\n"; + $code .= "DTSTAMP:" . gmdate('Ymd') . "T" . gmdate("His") . "Z\r\n"; + + $startTime = $this->icalFormatTime($time['start']); + $endTime = $this->icalFormatTime($time['end']); + + // The start day of the event MUST be offset by its day + // @TODO: Retrieve the timezone from php or the config file + $startDayNum = date("N", $termStart); // get the weekday number of the start day of the term + $day = date("Ymd", $this->firstDayAfterDate($times[0]['day'], $termStart)); //Calculate the start day of the course + + // Convert day number to abbreviation (based on iCal spec) for the RRULE + $dayAbbArray = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); + $dayAbb = array(); + foreach ($times as $time) { + array_push($dayAbb, $dayAbbArray[$time['day']]); //make $dayAbb an array using only the day abbreviations needed for the course. (MO,WE,FR) or (TU,TH) for example + } + $dayList = implode(',', $dayAbb); //Make a string to use in the RRULE + + $code .= "DTSTART;TZID=America/New_York:{$day}T{$startTime}\r\n"; + $code .= "DTEND;TZID=America/New_York:{$day}T{$endTime}\r\n"; + $code .= "RRULE:FREQ=WEEKLY;INTERVAL=1;WKST=MO;BYDAY={$dayList};UNTIL={$termEnd}\r\n"; //add the RRULE that allows the event to repeat across multiple days a week + $code .= "ORGANIZER:RIT\r\n"; + + // Course name + $code .= "SUMMARY:{$course['title']}"; + if($course['courseNum'] != 'non') { + $code .= " ({$course['courseNum']})"; + } + $code .= "\r\n"; + + // Meeting location + if($course['courseNum'] != 'non') { + $bldg = $times[0]['bldg']; + $code .= "LOCATION:{$bldg['code']}-{$times[0]['room']}\r\n"; + } + + $code .= "END:VEVENT\r\n"; + } + else { + //fallback to default behavior of creating a separate event for each time the course runs + // Iterate over all the times to create separate event series for each time + foreach($course['times'] as $time) { + $code .= "BEGIN:VEVENT\r\n"; + $code .= "UID:" . md5(uniqid(mt_rand(), true) . " @{$HTTPROOTADDRESS}"); + $code .= "\r\n"; + $code .= "TZID:America/New_York\r\n"; + $code .= "DTSTAMP:" . gmdate('Ymd') . "T" . gmdate("His") . "Z\r\n"; + + $startTime = $this->icalFormatTime($time['start']); + $endTime = $this->icalFormatTime($time['end']); + + // The start day of the event MUST be offset by it's day + // @TODO: Retrieve the timezone from php or the config file + $startDayNum = date("N", $termStart); // get the weekday number of the start day of the term + $day = date("Ymd", $this->firstDayAfterDate($times[0]['day'], $termStart)); //Calculate the start day of the course + + $code .= "DTSTART;TZID=America/New_York:{$day}T{$startTime}\r\n"; + $code .= "DTEND;TZID=America/New_York:{$day}T{$endTime}\r\n"; + $code .= "RRULE:FREQ=WEEKLY;UNTIL={$termEnd}\r\n"; + $code .= "ORGANIZER:RIT\r\n"; + + // Course name + $code .= "SUMMARY:{$course['title']}"; + if($course['courseNum'] != 'non') { + $code .= " ({$course['courseNum']})"; + } + $code .= "\r\n"; + + // Meeting location + if($course['courseNum'] != 'non') { + $bldg = $time['bldg'][$schedule['bldgStyle']]; + $code .= "LOCATION:{$bldg}-{$time['room']}\r\n"; + } + + $code .= "END:VEVENT\r\n"; + } } - - $code .= "END:VEVENT\r\n"; } - } $code .= "END:VCALENDAR\r\n";