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.2
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                        if(strtotime($event_time) < strtotime($data[FB_EVENTS_FROM_DATE])) continue;
211                        $json_link = "https://graph.facebook.com/v2.12/".$event_time['id']."/?fields={$fb_fields}&access_token={$fb_access_token}";
212                        array_push($events, json_decode($this->getData($json_link), true));
213                    }
214                    unset($events[$i]);
215                }
216            }
217
218			// Sort array of events by start time
219			if ($data[FB_EVENTS_SORT] === 'ASC') {
220				usort($events, 'compareEventStartDateAsc');
221			}
222			else {
223				usort($events, 'compareEventStartDateDesc');
224			}
225
226			// Iterate over events
227			foreach($events as $event){
228
229				date_default_timezone_set($event['timezone']);
230
231				$start_date = date($date_format, strtotime($event['start_time']));
232				$start_time = date($time_format, strtotime($event['start_time']));
233
234				if (!isset($event['end_time'])) {
235					$event['end_time'] = $event['start_time'];
236				}
237				$end_date = date($date_format, strtotime($event['end_time']));
238				$end_time = date($time_format, strtotime($event['end_time']));
239
240				$eid = $event['id'];
241				$name = $event['name'];
242
243				$description = isset($event['description']) ? $event['description'] : "";
244				// Limit?
245				if (isset($data[FB_EVENTS_LIMIT]) && ($data[FB_EVENTS_LIMIT] > 0)) {
246					if (strlen($description) > $data[FB_EVENTS_LIMIT]) {
247						$description = substr($description, 0, $data[FB_EVENTS_LIMIT]);
248						// Find the first occurance of a space
249						$index = strrpos ($description, ' ');
250						$description = substr($description, 0, $index).'…';
251					}
252				}
253				$description = str_replace("\r", '', $description);
254				$description = str_replace("\n", "\\\\\n", $description);
255
256				$picFull = isset($event['cover']['source']) ? $event['cover']['source'] : "https://graph.facebook.com/v2.7/{$fb_page_id}/picture";
257				if (strpos($picFull, '?') > 0) $picFull .= '&.jpg';
258				$picSmall = isset($event['photos']['data'][0]['picture']) ? $event['photos']['data'][0]['picture'] : "https://graph.facebook.com/v2.7/{$fb_page_id}/picture";
259				if (strpos($picSmall, '?') > 0) $picSmall .= '&.jpg';
260				$picSquare = isset($event['picture']['data']['url']) ? $event['picture']['data']['url'] : "https://graph.facebook.com/v2.7/{$fb_page_id}/picture";
261				if (strpos($picSquare, '?') > 0) $picSquare .= '&.jpg';
262
263				// place
264				$place_name = isset($event['place']['name']) ? $event['place']['name'] : "";
265				$street = isset($event['place']['location']['street']) ? $event['place']['location']['street'] : "";
266				$city = isset($event['place']['location']['city']) ? $event['place']['location']['city'] : "";
267				$country = isset($event['place']['location']['country']) ? $event['place']['location']['country'] : "";
268				$zip = isset($event['place']['location']['zip']) ? $event['place']['location']['zip'] : "";
269
270				$location="";
271				$location_address="";
272
273				if ($place_name && $street & $city && $country && $zip){
274					$location = "{$place_name}";
275					$location_address = "{$street}, {$zip} {$city}, {$country}";
276				}
277				else{
278					$location = '?';
279					$location_address = '?';
280				}
281
282				// Build the entry
283				$entry = $data[FB_EVENTS_TEMPLATE];
284
285				// Date
286				$dateStart = date($this->getConf(FB_EVENTS_DATE_FORMAT), strtotime($event['start_time']));
287				$dateEnd = date($this->getConf(FB_EVENTS_DATE_FORMAT), strtotime($event['end_time']));
288				$dateStart != $dateEnd ? $date = "{$dateStart} - {$dateEnd}" : $date = "{$dateStart}";
289				// Time
290				$timeStart = date($this->getConf(FB_EVENTS_TIME_FORMAT), strtotime($event['start_time']));
291				$timeEnd = date($this->getConf(FB_EVENTS_TIME_FORMAT), strtotime($event['end_time']));
292				$timeStart != $timeEnd ? $time = "{$timeStart} - {$timeEnd}" : $time = "{$timeStart}";
293				// DateTime
294				$dateTimeStart = date($this->getConf(FB_EVENTS_TIME_FORMAT).', '.$this->getConf(FB_EVENTS_DATE_FORMAT), strtotime($event['start_time']));
295				$dateTimeEnd = date($this->getConf(FB_EVENTS_TIME_FORMAT).', '.$this->getConf(FB_EVENTS_DATE_FORMAT), strtotime($event['end_time']));
296				if($dateStart != $dateEnd) {
297					$dateTime = $timeStart.', '.$dateStart.' - '.$timeEnd.', '.$dateEnd;
298				}
299				else {
300					$dateTime = $timeStart.' - '.$timeEnd.', '.$dateEnd;
301				}
302
303				// Metadata
304				$microdata = '<html><script type="application/ld+json">{json_microdata}</script></html>';
305                $json_microdata = array (
306                    '@context' => 'http://schema.org',
307                    '@type' => 'Event',
308                    'name' => '{title}',
309                    'startDate' => '{starttimestamp}',
310                    'endDate' => '{endtimestamp}',
311                    'url' => '{url}',
312                    'location' =>
313                    array (
314                        '@type' => 'Place',
315                        'name' => '{location}',
316                        'address' => '{location_address}',
317                    ),
318                    'description' => $description,
319                    'image' => '{image}',
320                );
321
322				$microdata = str_replace('{json_microdata}', json_encode($json_microdata), $microdata);
323				$entry = str_replace('{microdata}',$microdata, $entry);
324
325				// Replace the values
326				$entry = str_replace('{title}', $name, $entry);
327				$entry = str_replace('{description}', $description, $entry);
328				$entry = str_replace('{location}', $location, $entry);
329				$entry = str_replace('{location_address}', $location_address, $entry);
330				$entry = str_replace('{place}', $place_name, $entry);
331				$entry = str_replace('{city}', $city, $entry);
332				$entry = str_replace('{country}', $country, $entry);
333				$entry = str_replace('{zip}', $zip, $entry);
334				$entry = str_replace('{image}', $picFull, $entry);
335				$entry = str_replace('{image_large}', $picFull, $entry);
336				$entry = str_replace('{image_small}', $picSmall, $entry);
337				$entry = str_replace('{image_square}', $picSquare, $entry);
338				// Date & time replacements
339				$entry = str_replace('{date}', $dateStart, $entry);
340				$entry = str_replace('{time}', $time, $entry);
341				$entry = str_replace('{datetime}', $dateTime, $entry);
342				$entry = str_replace('{startdatetime}', $dateTimeStart, $entry);
343				$entry = str_replace('{startdate}', $dateStart, $entry);
344				$entry = str_replace('{starttime}', $timeStart, $entry);
345				$entry = str_replace('{enddatetime}', $dateTimeEnd, $entry);
346				$entry = str_replace('{enddate}', $dateEnd, $entry);
347				$entry = str_replace('{endtime}', $timeEnd, $entry);
348				$entry = str_replace('{timestamp}', date('c', strtotime($event['start_time'])), $entry);
349				$entry = str_replace('{starttimestamp}', date('c', strtotime($event['start_time'])), $entry);
350				$entry = str_replace('{endtimestamp}', date('c', strtotime($event['end_time'])), $entry);
351				// [[ url | read more ]
352				$event_url = "http://www.facebook.com/events/".$eid;
353				$entry = str_replace('{url}', $event_url, $entry);
354				$entry = str_replace('{more}', '[['.$event_url.'|'.$this->getLang('read_more').']]', $entry);
355
356				// Handle wall posts
357				$wallposts = '';
358				foreach($event['feed']['data'] as $post) {
359					$wallpost = $data[FB_EVENTS_WALLPOSTS_TEMPLATE];
360					$quoteprefix = $data[FB_EVENTS_QUOTE_PREFIX];
361
362					$userImage = $post['from']['picture']['data']['url'].'&.jpg';
363					$userName = $post['from']['name'];
364
365					$postDateTime = date($this->getConf(FB_EVENTS_DATE_FORMAT), strtotime($post['created_time']));
366
367					$postLink = $post['permalink_url'];
368
369					$description = $quoteprefix.$post['message'];
370					$description = str_replace("\r", '', $description);
371					$description = str_replace("\n", "\n{$quoteprefix}", $description);
372
373					if(isset($post['source'])) {
374						$mediaSource = $post['source'].'&.jpg';
375					}
376					elseif(isset($post['link'])) {
377						$mediaSource = $post['link'];
378					}
379					else {
380						$mediaSource = '';
381					}
382					isset($post['picture']) ? $mediaImage = $post['picture'].'&.jpg' : $mediaImage = '';
383
384					$wallpost = str_replace('{wp_userImage}', $userImage, $wallpost);
385					$wallpost = str_replace('{wp_userName}', $userName, $wallpost);
386					$wallpost = str_replace('{wp_datetime}', $postDateTime, $wallpost);
387					$wallpost = str_replace('{wp_content}', $description, $wallpost);
388					$wallpost = str_replace('{wp_mediaSource}', $mediaSource, $wallpost);
389					$wallpost = str_replace('{wp_mediaImage}', $mediaImage, $wallpost);
390					$wallpost = str_replace('{wp_permalink}', $postLink, $wallpost);
391
392					$wallposts .= $wallpost;
393				}
394				$entry = str_replace('{wallposts}', $wallposts, $entry);
395
396				// Add the entry to the content
397				$content .= $entry;
398			}
399
400			$html = p_render($mode, p_get_instructions($content), $info);
401			$renderer->doc .= $html;
402
403			// Set the timezone back to the original
404			date_default_timezone_set($origin_timezone);
405
406			return true;
407		}
408		return false;
409	}
410}
411