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