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