1<?php
2
3/**
4 * Plugin importfacebookevents: Displays facebook events.
5 *
6 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
7 * @version    3.1
8 * @date       March 2018
9 * @author     G. Surrel <gregoire.surrel.org>, J. Drost-Tenfelde <info@drost-tenfelde.de>
10 *
11 * This plugin uses Facebook's Graph API v2.12.
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
21// Syntax parameters
22define("FB_EVENTS_APPLICATION_ID", "fb_application_id");
23define("FB_EVENTS_APPLICATION_SECRET", "fb_application_secret");
24define("FB_EVENTS_FAN_PAGE_ID", "fanpageid");
25define("FB_EVENTS_SHOW_AS", "showAs");
26define("FB_EVENTS_WALLPOSTS_SHOW_AS", "showPostsAs");
27define("FB_EVENTS_FROM_DATE", "from");
28define("FB_EVENTS_TO_DATE", "to");
29define("FB_EVENTS_SORT", "sort");
30define("FB_EVENTS_NR_ENTRIES", "numberOfEntries");
31define("FB_EVENTS_SHOW_END_TIMES", "showEndTimes");
32define("FB_EVENTS_LIMIT", "limit");
33define("FB_EVENTS_QUOTE_PREFIX", "quoteprefix");
34
35// Configuration parameters
36define("FB_EVENTS_DATE_FORMAT", "dformat");
37define("FB_EVENTS_TIME_FORMAT", "tformat");
38define("FB_EVENTS_TEMPLATE", "template");
39define("FB_EVENTS_WALLPOSTS_TEMPLATE", "wallposts");
40
41// Helper sorting functions
42function compareEventStartDateAsc($a, $b) {
43	return strtotime($a['start_time']) - strtotime($b['start_time']);
44}
45function compareEventStartDateDesc($b, $a) {
46	return strtotime($a['start_time']) - strtotime($b['start_time']);
47}
48
49
50/**
51 * This plugin retrieves facebook events and displays them in HTML.
52 *
53 * Usage (simple): {{facebookevents>fanpageid=12345}}
54 * Usage (complex): {{facebookevents>fanpageid=12345&showAs=table&showPostsAs=wallposts_alternate&from=-2 weeks&to=today}}
55 *
56 */
57class syntax_plugin_importfacebookevents extends DokuWiki_Syntax_Plugin
58{
59	function getInfo() {
60	  return array(
61		'author' => 'G. Surrel, J. Drost-Tenfelde',
62		'email'  => '',
63		'date'   => '2018-03-08',
64		'name'   => 'import_facebook_events',
65		'desc'   => 'Displays facebook events as HTML',
66		'url'    => 'https://www.dokuwiki.org/plugin:import_facebook_events',
67	 );
68	}
69
70	// implement necessary Dokuwiki_Syntax_Plugin methods
71	function getType() {
72		return 'substition';
73	}
74
75	function connectTo($mode) {
76		$this->Lexer->addSpecialPattern('\{\{facebookevents.*?\}\}',$mode,'plugin_importfacebookevents');
77	}
78
79	function getData($url) {
80		$ch = curl_init();
81		$timeout = 5;
82		curl_setopt($ch, CURLOPT_URL, $url);
83		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
84		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
85		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
86		$data = curl_exec($ch);
87		curl_close($ch);
88		return $data;
89	}
90
91	/**
92	 * parse parameters from the {{facebookevents>...}} tag.
93	 * @return an array that will be passed to the renderer function
94	 */
95	function handle($match, $state, $pos, &$handler) {
96		$match = substr($match, 17, -2);
97		parse_str($match, $params);
98
99		// Make sure the necessary data is set
100		if ($this->getConf(FB_EVENTS_APPLICATION_ID) == '') {
101		  $this->error = /*$this->getLang*/('error_appid_not_set');
102		}
103		if ($this->getConf(FB_EVENTS_APPLICATION_SECRET) == '') {
104		  $this->error = /*$this->getLang*/('error_appsecret_not_set');
105		}
106		if (!$params[FB_EVENTS_FAN_PAGE_ID]) {
107		  $this->error = /*$this->getLang*/('error_fanpageid_not_set');
108		}
109		if (!$params[FB_EVENTS_SHOW_AS]) {
110			$params[FB_EVENTS_SHOW_AS] = 'default';
111		}
112		if (!$params[FB_EVENTS_WALLPOSTS_SHOW_AS]) {
113			$params[FB_EVENTS_WALLPOSTS_SHOW_AS] = 'wallposts_default';
114		}
115		if (!$params[FB_EVENTS_QUOTE_PREFIX]) {
116			$params[FB_EVENTS_QUOTE_PREFIX] = '> ';
117		}
118		if (!$params[FB_EVENTS_LIMIT]) {
119			$params[FB_EVENTS_LIMIT] = 0;
120		}
121
122		// Get the appropriate display template
123		$template = $this->getConf($params[FB_EVENTS_SHOW_AS]);
124		if (!isset($template) || $template == '') {
125			$template = $this->getConf('default');
126		}
127		$params[FB_EVENTS_TEMPLATE] = $template;
128
129		// Get the appropriate display template for comments
130		$wallposts_template = $this->getConf($params[FB_EVENTS_WALLPOSTS_SHOW_AS]);
131		if (!isset($wallposts_template) || $wallposts_template == '') {
132			$wallposts_template = $this->getConf('wallposts_default');
133		}
134		$params[FB_EVENTS_WALLPOSTS_TEMPLATE] = $wallposts_template;
135
136		// Sorting
137		if (!$params[FB_EVENTS_SORT]) {
138			$params[FB_EVENTS_SORT] = 'ASC';
139		}
140		elseif ($params[FB_EVENTS_SORT] != 'DESC') {
141			$params[FB_EVENTS_SORT] = 'ASC';
142		}
143
144		return $params;
145	}
146
147	/**
148	 * Retrieves the facebook events and parses them to HTML.
149	 */
150	function render($mode, &$renderer, $data) {
151		$info = $this->getInfo();
152
153		// Disable caching because the result depends on an external ressource
154		//$renderer->info['cache'] = false;
155
156		$content = '';
157
158		if ($mode == 'xhtml') {
159			// Catch errors
160			if ($this->error) {
161				$renderer->doc .= 'Error in Plugin '.$info['name'].': '.$this->error;
162				return;
163			}
164
165			// Get the date format
166			$date_format = $this->getConf(FB_EVENTS_DATE_FORMAT);
167			$time_format = $this->getConf(FB_EVENTS_TIME_FORMAT);
168
169			// Get the facebook information
170			$fb_app_id = $this->getConf(FB_EVENTS_APPLICATION_ID);
171			$fb_secret = $this->getConf(FB_EVENTS_APPLICATION_SECRET);
172			$fb_page_id = $data[FB_EVENTS_FAN_PAGE_ID];
173
174			// Get the access token using app-id and secret
175			$token_url ="https://graph.facebook.com/oauth/access_token?client_id={$fb_app_id}&client_secret={$fb_secret}&grant_type=client_credentials";
176			$token_data = $this->getData($token_url);
177
178			$elements = explode('"',$token_data);
179			if (count($elements) < 9) {
180				$renderer->doc .= 'Access token could not be retrieved for Plugin '.$info['name'].': '.$this->error.' | '.$token_data;
181				return;
182			}
183			$fb_access_token = $elements[3];
184
185			// Get the events
186			$since_date = strtotime("-2 month", $data[FB_EVENTS_FROM_DATE]); // Go back in time as recurrent events disappear
187			$until_date = strtotime($data[FB_EVENTS_TO_DATE]);
188			$limit = $data[FB_EVENTS_NR_ENTRIES];
189
190			$fb_fields="id,name,place,updated_time,timezone,start_time,end_time,event_times,cover,photos{picture},picture{url},description,feed.limit(10){from{name,picture},created_time,type,message,link,permalink_url,source,picture}";
191
192			$json_link = "https://graph.facebook.com/v2.12/{$fb_page_id}/events/?fields={$fb_fields}&access_token={$fb_access_token}&limit={$limit}&since={$since_date}&until={$until_date}";
193			$json = $this->getData($json_link);
194
195			dbglog(date(DATE_ATOM));
196			dbglog($json_link);
197
198			//$objects = json_decode($json, true, 512, JSON_BIGINT_AS_STRING);
199			$objects = json_decode($json, true);
200			$events = $objects['data'];
201
202			// Save timezone setting
203			$origin_timezone = date_default_timezone_get();
204
205            // Handle recurring events
206            foreach($events as $i => $event){
207                if(isset($event['event_times'])) {
208                    dbglog("Subevent found");
209                    foreach($event['event_times'] as $event_time) {
210                        $json_link = "https://graph.facebook.com/v2.12/".$event_time['id']."/?fields={$fb_fields}&access_token={$fb_access_token}";
211                        array_push($events, json_decode($this->getData($json_link), true));
212                    }
213                    unset($events[$i]);
214                }
215            }
216
217			// Sort array of events by start time
218			if ($data[FB_EVENTS_SORT] === 'ASC') {
219				usort($events, 'compareEventStartDateAsc');
220			}
221			else {
222				usort($events, 'compareEventStartDateDesc');
223			}
224
225			// Iterate over events
226			foreach($events as $event){
227
228				date_default_timezone_set($event['timezone']);
229
230				$start_date = date($date_format, strtotime($event['start_time']));
231				$start_time = date($time_format, strtotime($event['start_time']));
232
233                if(strtotime($event['start_time']) < strtotime($data[FB_EVENTS_FROM_DATE])) continue;
234
235				if (!isset($event['end_time'])) {
236					$event['end_time'] = $event['start_time'];
237				}
238				$end_date = date($date_format, strtotime($event['end_time']));
239				$end_time = date($time_format, strtotime($event['end_time']));
240
241				$eid = $event['id'];
242				$name = $event['name'];
243
244				$description = isset($event['description']) ? $event['description'] : "";
245				// Limit?
246				if (isset($data[FB_EVENTS_LIMIT]) && ($data[FB_EVENTS_LIMIT] > 0)) {
247					if (strlen($description) > $data[FB_EVENTS_LIMIT]) {
248						$description = substr($description, 0, $data[FB_EVENTS_LIMIT]);
249						// Find the first occurance of a space
250						$index = strrpos ($description, ' ');
251						$description = substr($description, 0, $index).'…';
252					}
253				}
254				$description = str_replace("\r", '', $description);
255				$description = str_replace("\n", "\\\\\n", $description);
256
257				$picFull = isset($event['cover']['source']) ? $event['cover']['source'] : "https://graph.facebook.com/v2.7/{$fb_page_id}/picture";
258				if (strpos($picFull, '?') > 0) $picFull .= '&.jpg';
259				$picSmall = isset($event['photos']['data'][0]['picture']) ? $event['photos']['data'][0]['picture'] : "https://graph.facebook.com/v2.7/{$fb_page_id}/picture";
260				if (strpos($picSmall, '?') > 0) $picSmall .= '&.jpg';
261				$picSquare = isset($event['picture']['data']['url']) ? $event['picture']['data']['url'] : "https://graph.facebook.com/v2.7/{$fb_page_id}/picture";
262				if (strpos($picSquare, '?') > 0) $picSquare .= '&.jpg';
263
264				// place
265				$place_name = isset($event['place']['name']) ? $event['place']['name'] : "";
266				$street = isset($event['place']['location']['street']) ? $event['place']['location']['street'] : "";
267				$city = isset($event['place']['location']['city']) ? $event['place']['location']['city'] : "";
268				$country = isset($event['place']['location']['country']) ? $event['place']['location']['country'] : "";
269				$zip = isset($event['place']['location']['zip']) ? $event['place']['location']['zip'] : "";
270
271				$location="";
272				$location_address="";
273
274				if ($place_name && $street & $city && $country && $zip){
275					$location = "{$place_name}";
276					$location_address = "{$street}, {$zip} {$city}, {$country}";
277				}
278				else{
279					$location = '?';
280					$location_address = '?';
281				}
282
283				// Build the entry
284				$entry = $data[FB_EVENTS_TEMPLATE];
285
286				// Date
287				$dateStart = date($this->getConf(FB_EVENTS_DATE_FORMAT), strtotime($event['start_time']));
288				$dateEnd = date($this->getConf(FB_EVENTS_DATE_FORMAT), strtotime($event['end_time']));
289				$dateStart != $dateEnd ? $date = "{$dateStart} - {$dateEnd}" : $date = "{$dateStart}";
290				// Time
291				$timeStart = date($this->getConf(FB_EVENTS_TIME_FORMAT), strtotime($event['start_time']));
292				$timeEnd = date($this->getConf(FB_EVENTS_TIME_FORMAT), strtotime($event['end_time']));
293				$timeStart != $timeEnd ? $time = "{$timeStart} - {$timeEnd}" : $time = "{$timeStart}";
294				// DateTime
295				$dateTimeStart = date($this->getConf(FB_EVENTS_TIME_FORMAT).', '.$this->getConf(FB_EVENTS_DATE_FORMAT), strtotime($event['start_time']));
296				$dateTimeEnd = date($this->getConf(FB_EVENTS_TIME_FORMAT).', '.$this->getConf(FB_EVENTS_DATE_FORMAT), strtotime($event['end_time']));
297				if($dateStart != $dateEnd) {
298					$dateTime = $timeStart.', '.$dateStart.' - '.$timeEnd.', '.$dateEnd;
299				}
300				else {
301					$dateTime = $timeStart.' - '.$timeEnd.', '.$dateEnd;
302				}
303
304				// Metadata
305				$microdata = '<html><script type="application/ld+json">{json_microdata}</script></html>';
306                $json_microdata = array (
307                    '@context' => 'http://schema.org',
308                    '@type' => 'Event',
309                    'name' => '{title}',
310                    'startDate' => '{starttimestamp}',
311                    'endDate' => '{endtimestamp}',
312                    'url' => '{url}',
313                    'location' =>
314                    array (
315                        '@type' => 'Place',
316                        'name' => '{location}',
317                        'address' => '{location_address}',
318                    ),
319                    'description' => $description,
320                    'image' => '{image}',
321                );
322
323				$microdata = str_replace('{json_microdata}', json_encode($json_microdata), $microdata);
324				$entry = str_replace('{microdata}',$microdata, $entry);
325
326				// Replace the values
327				$entry = str_replace('{title}', $name, $entry);
328				$entry = str_replace('{description}', $description, $entry);
329				$entry = str_replace('{location}', $location, $entry);
330				$entry = str_replace('{location_address}', $location_address, $entry);
331				$entry = str_replace('{place}', $place_name, $entry);
332				$entry = str_replace('{city}', $city, $entry);
333				$entry = str_replace('{country}', $country, $entry);
334				$entry = str_replace('{zip}', $zip, $entry);
335				$entry = str_replace('{image}', $picFull, $entry);
336				$entry = str_replace('{image_large}', $picFull, $entry);
337				$entry = str_replace('{image_small}', $picSmall, $entry);
338				$entry = str_replace('{image_square}', $picSquare, $entry);
339				// Date & time replacements
340				$entry = str_replace('{date}', $dateStart, $entry);
341				$entry = str_replace('{time}', $time, $entry);
342				$entry = str_replace('{datetime}', $dateTime, $entry);
343				$entry = str_replace('{startdatetime}', $dateTimeStart, $entry);
344				$entry = str_replace('{startdate}', $dateStart, $entry);
345				$entry = str_replace('{starttime}', $timeStart, $entry);
346				$entry = str_replace('{enddatetime}', $dateTimeEnd, $entry);
347				$entry = str_replace('{enddate}', $dateEnd, $entry);
348				$entry = str_replace('{endtime}', $timeEnd, $entry);
349				$entry = str_replace('{timestamp}', date('c', strtotime($event['start_time'])), $entry);
350				$entry = str_replace('{starttimestamp}', date('c', strtotime($event['start_time'])), $entry);
351				$entry = str_replace('{endtimestamp}', date('c', strtotime($event['end_time'])), $entry);
352				// [[ url | read more ]
353				$event_url = "http://www.facebook.com/events/".$eid;
354				$entry = str_replace('{url}', $event_url, $entry);
355				$entry = str_replace('{more}', '[['.$event_url.'|'.$this->getLang('read_more').']]', $entry);
356
357				// Handle wall posts
358				$wallposts = '';
359				foreach($event['feed']['data'] as $post) {
360					$wallpost = $data[FB_EVENTS_WALLPOSTS_TEMPLATE];
361					$quoteprefix = $data[FB_EVENTS_QUOTE_PREFIX];
362
363					$userImage = $post['from']['picture']['data']['url'].'&.jpg';
364					$userName = $post['from']['name'];
365
366					$postDateTime = date($this->getConf(FB_EVENTS_DATE_FORMAT), strtotime($post['created_time']));
367
368					$postLink = $post['permalink_url'];
369
370					$description = $quoteprefix.$post['message'];
371					$description = str_replace("\r", '', $description);
372					$description = str_replace("\n", "\n{$quoteprefix}", $description);
373
374					if(isset($post['source'])) {
375						$mediaSource = $post['source'].'&.jpg';
376					}
377					elseif(isset($post['link'])) {
378						$mediaSource = $post['link'];
379					}
380					else {
381						$mediaSource = '';
382					}
383					isset($post['picture']) ? $mediaImage = $post['picture'].'&.jpg' : $mediaImage = '';
384
385					$wallpost = str_replace('{wp_userImage}', $userImage, $wallpost);
386					$wallpost = str_replace('{wp_userName}', $userName, $wallpost);
387					$wallpost = str_replace('{wp_datetime}', $postDateTime, $wallpost);
388					$wallpost = str_replace('{wp_content}', $description, $wallpost);
389					$wallpost = str_replace('{wp_mediaSource}', $mediaSource, $wallpost);
390					$wallpost = str_replace('{wp_mediaImage}', $mediaImage, $wallpost);
391					$wallpost = str_replace('{wp_permalink}', $postLink, $wallpost);
392
393					$wallposts .= $wallpost;
394				}
395				$entry = str_replace('{wallposts}', $wallposts, $entry);
396
397				// Add the entry to the content
398				$content .= $entry;
399			}
400
401			$html = p_render($mode, p_get_instructions($content), $info);
402			$renderer->doc .= $html;
403
404			// Set the timezone back to the original
405			date_default_timezone_set($origin_timezone);
406
407			return true;
408		}
409		return false;
410	}
411}
412