1<?php
2
3/**
4 * Plugin iCalendar: Renders an iCal .ics file into HTML.
5 *
6 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
7 * @version    1.4
8 * @date       November 2011
9 * @author     J. Drost-Tenfelde <info@drost-tenfelde.de>
10 *
11 * This plugin is based on the iCalEvents plugin by Robert Rackl <wiki@doogie.de>.
12 *
13 */
14
15// must be run within Dokuwiki
16if(!defined('DOKU_INC')) die();
17
18if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
19require_once(DOKU_PLUGIN.'syntax.php');
20
21include_once(DOKU_INC.'lib/plugins/iCalendar/functions.php');
22
23/**
24 * This plugin gets an iCalendar file via HTTP and parses it into HTML.
25 *
26 * Usage: {{iCalendar>http://host/myCalendar.ics#from=today&previewDays=30}}
27 *
28 * You can filter the events that are shown with two parametes:
29 * 1. 'from' a date from which on to show events. MUST be in the format MM/dd/yyyy
30 *           or you can simplay say "from=today".
31 *           If from is ommited, then all events are shown.
32 * 2. 'previewDays' amount of days to preview into the future.
33 *
34 * <code>from <= eventdate <= from+(previewDays*24*60*3600)</code>
35 *
36 * There are some more configuration settins in plugins/iCalendar/conf/default.php
37 *
38 * @see http://de.wikipedia.org/wiki/ICalendar
39 */
40class syntax_plugin_iCalendar extends DokuWiki_Syntax_Plugin
41{
42    function getInfo() {
43      return array(
44        'author' => 'J. Drost-Tenfelde',
45        'email'  => 'info@drost-tenfelde.de',
46        'date'   => '2011-09-28',
47        'name'   => 'iCalendar',
48        'desc'   => 'Parses an iCalendar .ics file into HTML',
49        'url'    => 'http://www.drost-tenfelde.de/?id=dokuwiki:plugins:icalendar',
50      );
51    }
52
53    // implement necessary Dokuwiki_Syntax_Plugin methods
54    function getType() { return 'substition'; }
55    function getSort() { return 42; }
56    function connectTo($mode) { $this->Lexer->addSpecialPattern('\{\{iCalendar>.*?\}\}',$mode,'plugin_iCalendar'); }
57
58	/**
59	 * parse parameters from the {{iCalendar>...}} tag.
60	 * @return an array that will be passed to the renderer function
61	 */
62	function handle($match, $state, $pos, &$handler) {
63
64		$match = substr($match, 13, -2); // strip {{iCalendar> from start and }} from end
65		list($icsURL, $flagStr) = explode('#', $match);
66		parse_str($flagStr, $params);
67
68        // Get the from parameter
69		if ($params['from'] == 'today') {
70			$from = time();
71		} else if (preg_match('#(\d\d)/(\d\d)/(\d\d\d\d)#', $params['from'], $fromDate)) {
72			// must be MM/dd/yyyy
73			$from = mktime(0, 0, 0, $fromDate[1], $fromDate[2], $fromDate[3]);
74		} else if (preg_match('/\d+/', $params['from'])) {
75			$from = $params['from'];
76		}
77        // Get the to parameter
78		if ($params['to'] == 'today') {
79			$to = mktime(24, 0, 0, date("m") , date("d"), date("Y"));
80
81		} else if (preg_match('#(\d\d)/(\d\d)/(\d\d\d\d)#', $params['to'], $toDate)) {
82			// must be MM/dd/yyyy
83			$to = mktime(0, 0, 0, $toDate[1], $toDate[2], $toDate[3]);
84		} else if (preg_match('/\d+/', $params['to'])) {
85			$to = $params['to'];
86		}
87
88        // Get the numberOfEntries parameter
89        if ($params['numberOfEntries']) {
90			$numberOfEntries = $params['numberOfEntries'];
91		} else {
92			$numberOfEntries = -1;
93      	}
94
95        // Get the show end dates parameter
96		if ($params['showEndDates'] == 1 ) {
97    		$showEndDates = true;
98		} else {
99			$showEndDates = false;
100      	}
101        // Get the show as list parameter
102        if ( $params['showAs'] ) {
103            $showAs = $params['showAs'];
104        }
105        else {
106            $showAs = 'default';
107        }
108
109        // Get the showAs parameter (since v1.4)
110		if ( $params['showAs'] ) {
111            $showAs = $params['showAs'];
112		}
113		else {
114            // Backward compatibiltiy of v1.3 or earlier
115            if ($params['showAsList'] == 1) {
116                $showAs = 'list';
117            } else {
118                $showAs = 'default';
119            }
120        }
121        // Get the appropriate template
122        $template = $this->getConf($showAs);
123        if ( !isset($template ) || $template == '' ) {
124			$template = $this->getConf('default');
125		}
126
127        // Find out if the events should be sorted in reserve
128        $sort_descending = false;
129        if ( $params['sort'] == 'DESC') {
130            $sort_descending = true;
131        }
132        //echo $rsort;
133        //exit( 0 );
134
135        // Get the previewDays parameter
136        if ( $params['previewDays'] ) {
137            $previewDays = $params['previewDays'];
138        }
139        else {
140            $previewDays = -1;
141        }
142
143		#echo "url=$icsURL from = $from    numberOfEntries = $numberOfEntries<br>";
144		return array($icsURL, $from, $to, $previewDays, $numberOfEntries, $showEndDates, $template, $sort_descending);
145	}
146
147	/**
148	 * loads the ics file via HTTP, parses it and renders an HTML table.
149	 */
150	function render($mode, &$renderer, $data) {
151		list($url, $from, $to, $previewDays, $numberOfEntries, $showEndDates, $template, $sort_descending) = $data;
152		$ret = ''.$mediadir;
153
154		if ($mode == 'xhtml') {
155			# parse the ICS file
156			$entries = $this->_parseIcs($url, $from, $to, $previewDays, $numberOfEntries, $sort_descending);
157
158			if ($this->error) {
159				$renderer->doc .= "Error in Plugin iCalendar: ".$this->error;
160				return true;
161			}
162
163			#loop over entries and create a table row for each one.
164			$rowCount = 0;
165
166			foreach ($entries as $entry) {
167				$rowCount++;
168
169				# Get the html for the entries
170				$entryTemplate = $template;
171
172				// {description}
173				$entryTemplate = str_replace('{description}', $entry['description'], $entryTemplate );
174
175                // {summary}
176                $entryTemplate = str_replace('{summary}', $entry['summary'], $entryTemplate );
177
178                // {summary_link}
179                $summary_link = array();
180                $summary_link['class']  = 'urlintern';
181                $summary_link['style']  = 'background-image: url(lib/plugins/iCalendar/ics.png); background-repeat:no-repeat; padding-left:16px; text-decoration: none;';
182                $summary_link['pre']    = '';
183                $summary_link['suf']    = '';
184                $summary_link['more']   = 'rel="nofollow"';
185                $summary_link['target'] = '';
186                $summary_link['title']  = $entry['summary'];
187                $summary_link['url']    = 'lib/plugins/iCalendar/vevent.php?vevent='.urlencode( $entry['vevent'] );
188                $summary_link['name']  = $entry['summary'];
189                $entryTemplate = str_replace('{summary_link}', '<html>'.$renderer->_formatLink($summary_link).'</html>', $entryTemplate );
190
191                // See if a location was set
192				$location = $entry['location'];
193				if ( $location != '' ) {
194                    // {location}
195					$entryTemplate = str_replace('{location}', $location, $entryTemplate );
196
197					// {location_link}
198					$location_link = 'http://maps.google.com/maps?q='.str_replace(' ', '+', str_replace(',', ' ', $location));
199					$entryTemplate = str_replace('{location_link}', '[['.$location_link.'|'.$location.']]', $entryTemplate );
200				}
201				else {
202				    // {location}
203					$entryTemplate = str_replace('{location}', 'Unknown', $entryTemplate );
204					// {location_link}
205					$entryTemplate = str_replace('{location_link}', 'Unknown', $entryTemplate );
206				}
207
208				$dateString = "";
209
210				// Get the start and end day
211				$startDay = date("Ymd", $entry['startunixdate']);
212				$endDay = date("Ymd", $entry['endunixdate']);
213
214				if ( $endDay > $startDay )
215				{
216					if ( $entry['allday'] )
217					{
218						$dateString = $entry['startdate'].'-'.$entry['enddate'];
219					}
220					else {
221						$dateString = $entry['startdate'].' '.$entry['starttime'].'-'.$entry['enddate'].' '.$entry['endtime'];
222					}
223				}
224				else {
225					if ( $showEndDates ) {
226						if ( $entry['allday'] )
227						{
228							$dateString = $entry['startdate'];
229						}
230						else {
231							$dateString = $entry['startdate'].' '.$entry['starttime'].'-'.$entry['endtime'];
232						}
233					}
234					else {
235						$dateString = $entry['startdate'];
236					}
237				}
238
239				// {date}
240				$entryTemplate = str_replace('{date}', $dateString, $entryTemplate );
241
242				$ret .= $entryTemplate.'
243';
244
245			}
246			//$renderer->doc .= $ret;
247			$html = p_render($mode, p_get_instructions( $ret ), $info );
248			$html = str_replace( '\\n', '<br />', $html );
249			$renderer->doc .= $html;
250
251			return true;
252		}
253		return false;
254	}
255
256	/**
257	 * Load the iCalendar file from 'url' and parse all
258	 * events that are within the range
259	 * from <= eventdate <= from+previewSec
260	 *
261	 * @param url HTTP URL of an *.ics file
262	 * @param from unix timestamp in seconds (may be null)
263	 * @param to unix timestamp in seconds (may be null)
264	 * @param previewDays Limit the entries to 30 days in the future
265	 * @param numberOfEntries Number of entries to display
266	 * @param $sort_descending
267	 * @return an array of entries sorted by their startdate
268	 */
269	function _parseIcs($url, $from, $to, $previewDays, $numberOfEntries, $sort_descending ) {
270	    global $conf;
271
272		$http    = new DokuHTTPClient();
273		if (!$http->get($url)) {
274			$this->error = "Could not get '$url': ".$http->status;
275			return array();
276		}
277		$content    = $http->resp_body;
278		$entries    = array();
279
280		# If dateformat is set in plugin configuration ('dformat'), then use it.
281		# Otherwise fall back to dokuwiki's default dformat from the global /conf/dokuwiki.php.
282		$dateFormat = $this->getConf('dformat') ? $this->getConf('dformat') : $conf['dformat'];
283		//$timeFormat = $this->getConf('tformat') ? $this->getConf('tformat') : $conf['tformat'];
284
285		# regular expressions for items that we want to extract from the iCalendar file
286		$regex_vevent      = '/BEGIN:VEVENT(.*?)END:VEVENT/s';
287
288		#split the whole content into VEVENTs
289		preg_match_all($regex_vevent, $content, $matches, PREG_PATTERN_ORDER);
290
291        if ( $previewDays > 0 )
292        {
293            $previewSec = $previewDays * 24 * 3600;
294        }
295        else {
296            $previewSec = -1;
297        }
298
299		// loop over VEVENTs and parse out some itmes
300		foreach ($matches[1] as $vevent) {
301            $entry = parse_vevent( $vevent, $dateFormat );
302
303			// if entry is to old then filter it
304			if ($from && $entry['endunixdate']) {
305				if ($entry['endunixdate'] < $from) { continue; }
306				if (($previewSec > 0) && ($entry['startunixdate'] > time()+$previewSec)) { continue; }
307			}
308
309			// if entry is to new then filter it
310			if ($to && $entry['startunixdate']) {
311				if ($entry['startunixdate'] > $to) { continue; }
312			}
313
314			$entries[] = $entry;
315		}
316
317		if ( $to && ($from == null) )
318		{
319			// sort entries by startunixdate
320			usort($entries, 'compareByEndUnixDate');
321		} else if ( $from ) {
322			// sort entries by startunixdate
323			usort($entries, 'compareByStartUnixDate');
324        }
325        else if ( $sort_descending ) {
326            $entries = array_reverse( $entries, true );
327        }
328
329		// See if a maximum number of entries was set
330		if ( $numberOfEntries > 0 )
331		{
332            $entries = array_slice( $entries, 0, $numberOfEntries );
333
334
335            // Reverse array?
336            if ( $from && $sort_descending) {
337                $entries = array_reverse( $entries, true );
338            }
339            else if ( $to && !$from && (!$sort_descending)) {
340                $entries = array_reverse( $entries, true );
341            }
342		}
343
344		return $entries;
345	}
346}
347
348/** compares two entries by their startunixdate value */
349function compareByStartUnixDate($a, $b) {
350  return strnatcmp($a['startunixdate'], $b['startunixdate']);
351}
352
353/** compares two entries by their startunixdate value */
354function compareByEndUnixDate($a, $b) {
355  return strnatcmp($b['endunixdate'], $a['endunixdate']);
356}
357
358?>