Introduction
This tutorial is part of the Building Your Startup With PHP series on Envato Tuts+. In this series, I'm guiding you through launching a startup from concept to reality using my Meeting Planner app as a real-life example. Every step along the way, I'll release the Meeting Planner code as open-source examples you can learn from. I'll also address startup-related business issues as they arise.
I'm also pleased to announce that Meeting Planner is ready to try out. You can send meeting invitations, gather feedback from your participant, and finalize the meeting, importing the iCal file I'll describe today into your calendars. So, visit MeetingPlanner.io and give it a try.
What Does This Episode Cover?
In the last tutorial, I described how I constructed command links and coded operations for participants who begin responding to email invitations, i.e. viewing the meeting page or accepting or rejecting a place or time.
I also mentioned how a woman I'd been dating suggested that she didn't know if or when she'd see me again without a meeting planner invitation, which I soon provided. And then she said she wouldn't know when or where to show up without a Google calendar entry. In today's tutorial, I'll describe how I built the iCal feature to deliver an .ics file she could import for a successful date. It's always helpful to have someone motivating development, but it's usually been colleagues or a development manager.
Just a reminder, all of the code for Meeting Planner is written in the Yii2 Framework for PHP. If you'd like to learn more about Yii2, check out our parallel series Programming With Yii2 at Envato Tuts+.
I do participate in the comment threads below and am especially interested if you have additional ideas or want to suggest topics for future tutorials. You can also reach me on Twitter @reifman.
What Is an iCal File?
Defining iCalendar, Wikipedia says:
iCalendar is a computer file format which allows Internet users to send meeting requests and tasks to other Internet users, via email, or sharing files with an extension of .ics.
Basically, it's a file with the .ics extension that includes pertinent information about a meeting, the organizer and attendees, the date, time and duration, the location and more, all in a format that a variety of platforms and calendar services can recognize.
While iCalendar has advanced features, at this stage of Meeting Planner's Minimum Viable Product (MVP), I wanted to ensure that our scheduled meetings could be easily imported into Google Calendar, Apple Calendar and Microsoft Outlook and be recognized by web mail services. I'll work later to extend its functionality.
Through earlier tutorials, I'd built the functionality that collected all the necessary information such as participants, dates and times and locations. Now, I just needed to publish the event details in an attachment and deliver it via email.
Building the .ics File
Instead of writing .ics code from scratch, I began with Ahmad Amin's ics File Generator and customized it. I placed the code in /common/models/Calendar.php
.
iCal files are different dependent on who the user is. In other words, they must be personalized for delivery. Primarily, the links back to Meeting Planner need to be customized for authentication to the holder of that particular .ics file.
During the Meeting finalization process, we build an event file for each attendee with unique, authenticated links:
foreach ($attendees as $cnt=>$a) { ... $icsPath = Meeting::buildCalendar($this->id,$chosenPlace,$chosenTime,$attendees);
Within the Meeting model, the buildCalendar
method puts together all the data the generator will need for each user:
public static function buildCalendar($id,$chosenPlace,$chosenTime,$attendees) { $meeting = Meeting::findOne($id); $invite = new \common\models\Calendar(); $start_time = $chosenTime->start+(3600*7); // temp timezone adjust $end_time = $start_time+3600; // to do - allow length on meetings for end time calculation $sdate = new \DateTime(date("Y-m-d h:i:sA",$start_time), new \DateTimeZone('PST')); $edate = new \DateTime(date("Y-m-d h:i:sA",$end_time), new \DateTimeZone('PST')); $description = $meeting->message; // check if its a conference with no location if ($chosenPlace!==false) { if ($chosenPlace->place->website<>'') { $description.=' Website: '.$chosenPlace->place->website; } $location = str_ireplace(',',' ',$chosenPlace->place->name.' '.str_ireplace(', United States','',$chosenPlace->place->full_address)); } else { $location =''; } $invite ->setSubject($meeting->subject) ->setDescription($description) ->setStart($sdate) ->setEnd($edate) ->setLocation($location) ->setOrganizer($meeting->owner->email, $meeting->owner->username); foreach ($attendees as $a) { $invite ->addAttendee($a['email'], $a['username']) ->setUrl(\common\components\MiscHelpers::buildCommand($id,Meeting::COMMAND_VIEW,0,$a['user_id'],$a['auth_key'])); } $invite->generate() // generate the invite ->save(); // save it to a file; $downloadLink = $invite->getSavedPath(); return $downloadLink; }
If there's a location (for in-person meetings), I include a link to the place's website (if available) and an address for embedded maps. I've decide to code dates and times in UTC format for simplicity.
The common meeting information is set up, and then the organizer and attendees are added.
Here's what a sample file looks like when it's complete:
BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN METHOD:PUBLISH BEGIN:VEVENT UID:[email protected] DTSTART:20160506T013000Z DTEND:20160506T023000Z DTSTAMP:20160506T013000Z ORGANIZER;CN=admin:mailto:[email protected] URL;VALUE=URI:http://localhost:8888/mp/index.php/meeting/command?id=45&cmd=10&actor_id=1&k=ESxJU_2ZRhZIgzHFyJAIiC39RhZuLiM_&obj_id=0 ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=robsmith;X-NUM-GUESTS=0:mailto:[email protected] ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=admin;X-NUM-GUESTS=0:mailto:[email protected] CREATED: DESCRIPTION:It was fun running into you - let's definitely grab that beer! Website: http://www.patxispizza.com/ LAST-MODIFIED:20160506T013000Z LOCATION:Patxi's Pizza Ballard 5323 Ballard Ave NW Seattle WA 98107 SUMMARY:Meetup for Pizza and Long Delayed Conversation SEQUENCE:0 TRANSP:OPAQUE END:VEVENT END:VCALENDAR
Delivering the .ics File as an Attachment
In this earlier tutorial, I described using Yii2's built-in mailing support with the SwiftMailer extension to deliver meeting invitations. For transport delivery, I used Mailgun's SMTP service integration.
Certainly, in the past, integrating attachments into messages was complicated, but now it's pretty easy with Yii. Here's the code to add the file to the outbound meeting invitation:
// send the message $message = Yii::$app->mailer->compose([ 'html' => 'finalize-html', 'text' => 'finalize-text' ], [ 'meeting_id' => $this->id, 'noPlaces' => $noPlaces, 'participant_id' => 0, 'owner' => $this->owner->username, 'user_id' => $a['user_id'], 'auth_key' => $a['auth_key'], 'intro' => $this->message, 'links' => $links, 'header' => $header, 'chosenPlace' => $chosenPlace, 'chosenTime' => $chosenTime, 'notes' => $notes, 'meetingSettings' => $this->meetingSettings, ]); $icsPath = Meeting::buildCalendar($this->id,$chosenPlace,$chosenTime,$attendees); $message->setFrom(array('[email protected]'=>$this->owner->email)); $message->attachContent(file_get_contents($icsPath), ['fileName' => 'meeting.ics', 'contentType' => 'text/plain']); $message->setTo($a['email']) ->setSubject(Yii::t('frontend','Meeting Confirmed: ').$this->subject) ->send();
I just needed to use the $icsPath
returned above and use the attachContent
method to include it with the invitation email.
Here's what it looked like when it arrived in Gmail:
When I opened the invitation file in Mac OS, Apple Calendar presented this first step to select a calendar to add the event to:
Here's what it looks like in my calendar's day view:
Once the event is added, it will look like this when you click on it:
Overall, event generation turned out to be a fairly modest amount of work and successful in its implementations. Alpha people were impressed, and we used it for actual in-person meetings.
Issues During Development
While I was learning about .ics and experimenting with it, my webmail accounts on FastMail only intermittently recognized my .ics files as calendar invites. I needed to update the properties I included for it to be recognized in a friendly manner.
More correct structuring of the .ics file contents allowed FastMail to recognize the attachment automatically:
Unfortunately, its Add to Calendar button links to its own calendar, which I don't use. Again, to add to Apple Calendar, as with Gmail, I had to download and open the .ics file.
Mac OS seemed to recognize the files as soon as they were opened. It would direct me towards a couple of steps of creating an Apple Calendar event (shown above).
I also needed to set a unique ID for the events so that we could send updates in the future. For now, our database meeting ID with '@meetingplanner.io' suffices, e.g. uid = [email protected].
Ensuring that time zones were adequately delivered in .ics file formats required a bit of additional research. For simplicity, I'm relying on Amin's ics generator to code dates and times in UTC format. I have more work in Meeting Planner to manage time zones for users.
I'd also left out duration and end time from Meeting Planner. This will be something I have to patch soon, likely just adding a duration (in hours) for the average meeting and allowing people to extend it as necessary.
Future Issues
Ultimately, I'd like Meeting Planner to use a broader extent of iCal's functionality. Initially, I just wanted to get the core functionality working for the MVP. Here are a few ideas below.
I may want to create a user setting in Meeting Planner that would add alarms to people's calendar entries. In iCal, this is called VALARM
, e.g.:
BEGIN:VALARM TRIGGER:-PT1440M ACTION:DISPLAY DESCRIPTION:Reminder END:VALARM
When people update the original event, even after it's finalized, I'll want to support updates to the event in their calendar. This requires delivering a VEVENT
with an identical UID
but with an incremental SEQUENCE
(via TutorialsBag):
BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN METHOD:REQUEST BEGIN:VEVENT UID:[email protected] DTSTART:20130617T050000Z DTEND:20130617T075900Z DTSTAMP:20130616T050000Z ORGANIZER;mailto:[email protected] DESCRIPTION:The is a test invite for you to see how this thing actually works LAST-MODIFIED:20130616T050000Z LOCATION:Queens, New York SUMMARY:Test Demo Invite SEQUENCE:1 TRANSP:OPAQUE END:VEVENT END:VCALENDAR
Currently, I'm not tracking a sequential field for updates.
I'd also want to support cancellation files that delete the event from calendars. So, if a person cancels with Meeting Planner, we send an iCal file to attendees which removes the event from their calendar:
METHOD:CANCEL STATUS:CANCELLED
Over time, I'd like to cache .ics files for each event and participant for performance and storage issues. I'd like to provide some file management to reduce the eventual disk space required to store these, as they are only needed for a short amount of time and can be easily regenerated on demand.
I also want to ensure the directory in which these are stored is secured so that they can't be compromised, as they include a lot of personal and private information, such as email addresses and exactly where you can find specific people at a specific time.
In the future, I'd also like to try to integrate iCal's less regularly used advanced features to connect directly to Meeting Planner. For example, we'll be able to cancel or reschedule meetings from your iCal calendar program using Meeting Planner's programmatic interfaces, rather than having to return to the Meeting Planner website.
What's Next?
Watch for upcoming tutorials in our Building Your Startup With PHP series. In the next episode, I'll describe how to add support for registration via Google, Facebook and Twitter as well as connecting existing accounts to these social networks for the purpose of speeding adoption and simplifying usage.
Again, Meeting Planner is ready for you to begin using. Give it a try and schedule a meeting now!
Please feel free to add your questions and comments below; I try to participate in the discussions on a regular basis. You can also reach me on Twitter @reifman.
I'm also beginning to experiment with WeFunder based on the implementation of the SEC's new crowdfunding rules. Please consider following our profile. I may write about this more as part of this series.
Comments