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