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