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