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