1<?php 2/** 3 * DokuSIOC - SIOC plugin for DokuWiki 4 * 5 * DokuSIOC integrates the SIOC ontology within DokuWiki and provides an 6 * alternate RDF/XML views of the wiki documents. 7 * 8 * For DokuWiki we can't use the Triplify script because DokuWiki has not a RDBS 9 * backend. But the wiki API provides enough methods to get the data out, so 10 * DokuSIOC as a plugin uses the export hook to provide accessible data as 11 * RDF/XML, using the SIOC ontology as vocabulary. 12 * @copyright 2009 Michael Haschke 13 * @copyright 2020 mprins 14 * LICENCE 15 * 16 * This program is free software: you can redistribute it and/or modify it under 17 * the terms of the GNU General Public License as published by the Free Software 18 * Foundation, version 2 of the License. 19 * 20 * This program is distributed in the hope that it will be useful, but WITHOUT 21 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 22 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 23 * 24 * @link http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU General Public License 2.0 (GPLv2) 25 * 26 */ 27 28class action_plugin_dokusioc extends DokuWiki_Action_Plugin 29{ 30 31 private $agentlink = 'http://eye48.com/go/dokusioc?v=0.1.2'; 32 33 /** 34 * Register it's handlers with the DokuWiki's event controller 35 */ 36 public function register(Doku_Event_Handler $controller): void 37 { 38 //print_r(headers_list()); die(); 39 // test the requested action 40 $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'checkAction', $controller); 41 } 42 43 /* -- Event handlers ---------------------------------------------------- */ 44 45 public function checkAction($action, $controller) 46 { 47 global $INFO; 48 //print_r($INFO); die(); 49 //print_r(headers_list()); die(); 50 51 if ($action->data === 'export_siocxml') { 52 // give back rdf 53 $this->exportSioc(); 54 } elseif (($action->data === 'show' || $action->data === 'index') && $INFO['perm'] && !defined( 55 'DOKU_MEDIADETAIL' 56 ) && ($INFO['exists'] || getDwUserInfo($INFO['id'], $this)) && !isHiddenPage($INFO['id'])) { 57 if ($this->isRdfXmlRequest()) { 58 // forward to rdfxml document if requested 59 // print_r(headers_list()); die(); 60 $location = $this->createRdfLink(); 61 if (function_exists('header_remove')) { 62 header_remove(); 63 } 64 header('Location: ' . $location['href'], true, 303); 65 exit(); 66 } else { 67 // add meta link to html head 68 $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'createRdfLink'); 69 } 70 } 71 /* 72 else 73 { 74 print_r(array($action->data, $INFO['perm'], defined('DOKU_MEDIADETAIL'), $INFO['exists'], 75 getDwUserInfo($INFO['id'],$this), isHiddenPage($INFO['id']))); 76 die(); 77 } 78 */ 79 } 80 81 public function exportSioc() 82 { 83 global $ID, $INFO; 84 85 if (isHiddenPage($ID)) { 86 $this->exit("HTTP/1.0 404 Not Found"); 87 } 88 89 $sioc_type = $this->getContenttype(); 90 91 // Test for valid types 92 if (!(($sioc_type == 'post' && $INFO['exists']) || $sioc_type == 'user' || $sioc_type == 'container')) { 93 $this->exit("HTTP/1.0 404 Not Found"); 94 } 95 96 // Test for permission 97 if (!$INFO['perm']) { 98 // not enough rights to see the wiki page 99 $this->exit("HTTP/1.0 401 Unauthorized"); 100 } 101 102 // Forward to URI with explicit type attribut 103 if (!isset($_GET['type'])) { 104 header('Location:' . $_SERVER['REQUEST_URI'] . '&type=' . $sioc_type, true, 302); 105 } 106 107 // Include SIOC libs 108 require_once(__DIR__ . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'sioc_inc.php'); 109 require_once(__DIR__ . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'sioc_dokuwiki.php'); 110 111 // Create exporter 112 113 $rdf = new SIOCExporter(); 114 $rdf->profile_url = normalizeUri( 115 getAbsUrl( 116 exportlink( 117 $ID, 118 'siocxml', 119 array('type' => $sioc_type), 120 false, 121 '&' 122 ) 123 ) 124 ); 125 $rdf->setURLParameters('type', 'id', 'page', false); 126 127 // Create SIOC-RDF content 128 129 switch ($sioc_type) { 130 case 'container': 131 $rdf = $this->exportContainercontent($rdf); 132 break; 133 134 case 'user': 135 $rdf = $this->exportUsercontent($rdf); 136 break; 137 138 case 'post': 139 default: 140 $rdf = $this->exportPostcontent($rdf); 141 break; 142 } 143 144 // export 145 if ($this->getConf('noindx')) { 146 header("X-Robots-Tag: noindex", true); 147 } 148 $rdf->export(); 149 150 //print_r(headers_list()); die(); 151 die(); 152 } 153 154 private function exit($headermsg) 155 { 156 header($headermsg); 157 die(); 158 } 159 160 /* -- public class methods ---------------------------------------------- */ 161 162 private function getContenttype() 163 { 164 global $ID, $conf; 165 166 // check for type if unknown 167 if (!($_GET['type'] ?? "")) { 168 $userinfo = getDwUserInfo($ID, $this); 169 170 if ($userinfo) { 171 $type = 'user'; 172 } elseif (isset($_GET['do']) && $_GET['do'] == 'index') { 173 $type = 'container'; 174 } else { 175 $type = 'post'; 176 } 177 } else { 178 $type = $_GET['type']; 179 } 180 181 return $type; 182 } 183 184 private function exportContainercontent($exporter) 185 { 186 global $ID, $INFO, $conf; 187 188 if ($ID == $conf['start']) { 189 $title = $conf['title']; 190 } elseif (isset($INFO['meta']['title'])) { 191 $title = $INFO['meta']['title']; 192 } else { 193 $title = $ID; 194 } 195 196 $exporter->setParameters( 197 'Container: ' . $title, 198 getAbsUrl(), 199 getAbsUrl() . 'doku.php?', 200 'utf-8', 201 $this->agentlink 202 ); 203 204 // create container object 205 $wikicontainer = new SIOCDokuWikiContainer( 206 $ID, normalizeUri($exporter->siocURL('container', $ID)) 207 ); 208 209 /* container is type=wiki */ 210 if ($ID == $conf['start']) { 211 $wikicontainer->isWiki(); 212 } 213 /* sioc:name */ 214 if ($INFO['exists']) { 215 $wikicontainer->addTitle($INFO['meta']['title']); 216 } 217 /* has_parent */ 218 if ($INFO['namespace']) { 219 $wikicontainer->addParent($INFO['namespace']); 220 } 221 222 // search next level entries (posts, sub containers) in container 223 require_once(DOKU_INC . 'inc/search.php'); 224 $dir = utf8_encodeFN(str_replace(':', '/', $ID)); 225 $entries = array(); 226 $posts = array(); 227 $containers = array(); 228 search($entries, $conf['datadir'], 'search_index', array('ns' => $ID), $dir); 229 foreach ($entries as $entry) { 230 if ($entry['type'] === 'f') { 231 // wikisite 232 $posts[] = $entry; 233 } elseif ($entry['type'] === 'd') { 234 // sub container 235 $containers[] = $entry; 236 } 237 } 238 239 // without sub content it can't be a container (so it does not exist as a container) 240 if (count($posts) + count($containers) == 0) { 241 $this->exit("HTTP/1.0 404 Not Found"); 242 } 243 244 if (count($posts) > 0) { 245 $wikicontainer->addArticles($posts); 246 } 247 if (count($containers) > 0) { 248 $wikicontainer->addContainers($containers); 249 } 250 251 //print_r($containers);die(); 252 253 // add container to exporter 254 $exporter->addObject($wikicontainer); 255 256 return $exporter; 257 } 258 259 /* -- private helpers --------------------------------------------------- */ 260 261 private function exportUsercontent($exporter) 262 { 263 global $ID; 264 265 // get user info 266 $userinfo = getDwUserInfo($ID, $this); 267 268 // no userinfo means there is no user space or user does not exists 269 if ($userinfo === false) { 270 $this->exit("HTTP/1.0 404 Not Found"); 271 } 272 273 $exporter->setParameters( 274 'Account: ' . $userinfo['name'], 275 getAbsUrl(), 276 getAbsUrl() . 'doku.php?', 277 'utf-8', 278 $this->agentlink 279 ); 280 // create user object 281 //print_r($userinfo); die(); 282 // $id, $url, $userid, $name, $email 283 $wikiuser = new SIOCDokuWikiUser( 284 $ID, normalizeUri($exporter->siocURL('user', $ID)), $userinfo['user'], $userinfo['name'], $userinfo['mail'] 285 ); 286 /* TODO: avatar (using Gravatar) */ /* TODO: creator_of */ 287 // add user to exporter 288 $exporter->addObject($wikiuser); 289 290 //print_r(headers_list());die(); 291 return $exporter; 292 } 293 294 private function exportPostcontent($exporter) 295 { 296 global $ID, $INFO, $REV, $conf; 297 298 $exporter->setParameters( 299 $INFO['meta']['title'] . ($REV ? ' (rev ' . $REV . ')' : ''), 300 $this->getDokuUrl(), 301 $this->getDokuUrl() . 'doku.php?', 302 'utf-8', 303 $this->agentlink 304 ); 305 306 // create user object 307 $dwuserpage_id = cleanID($this->getConf('userns')) . ($conf['useslash'] ? '/' : ':') . $INFO['editor']; 308 // create wiki page object 309 $wikipage = new SIOCDokuWikiArticle( 310 $ID, // id 311 normalizeUri( 312 $exporter->siocURL( 313 'post', 314 $ID . ($REV ? $exporter->_urlseparator . 'rev' . $exporter->_urlequal . $REV : '') 315 ) 316 ), // url 317 $INFO['meta']['title'] . ($REV ? ' (rev ' . $REV . ')' : ''), // subject 318 rawWiki($ID, $REV) // body (content) 319 ); 320 /* encoded content */ 321 $wikipage->addContentEncoded(p_cached_output(wikiFN($ID, $REV), 'xhtml')); 322 /* created */ 323 if (isset($INFO['meta']['date']['created'])) { 324 $wikipage->addCreated(date('c', $INFO['meta']['date']['created'])); 325 } 326 /* or modified */ 327 if (isset($INFO['meta']['date']['modified'])) { 328 $wikipage->addModified(date('c', $INFO['meta']['date']['modified'])); 329 } 330 /* creator/modifier */ 331 if ($INFO['editor'] && $this->getConf('userns')) { 332 $wikipage->addCreator(array('foaf:maker' => '#' . $INFO['editor'], 'sioc:modifier' => $dwuserpage_id)); 333 } 334 /* is creator */ 335 if (isset($INFO['meta']['date']['created'])) { 336 $wikipage->isCreator(); 337 } 338 /* intern wiki links */ 339 $wikipage->addLinks($INFO['meta']['relation']['references']); 340 341 // contributors - only for last revision b/c of wrong meta data for older revisions 342 if (!$REV && $this->getConf('userns') && isset($INFO['meta']['contributor'])) { 343 $cont_temp = array(); 344 $cont_ns = $this->getConf('userns') . ($conf['useslash'] ? '/' : ':'); 345 foreach ($INFO['meta']['contributor'] as $cont_id => $cont_name) { 346 $cont_temp[$cont_ns . $cont_id] = $cont_name; 347 } 348 $wikipage->addContributors($cont_temp); 349 } 350 351 // backlinks - only for last revision 352 if (!$REV) { 353 require_once(DOKU_INC . 'inc/fulltext.php'); 354 $backlinks = ft_backlinks($ID); 355 if (count($backlinks) > 0) { 356 $wikipage->addBacklinks($backlinks); 357 } 358 } 359 360 // TODO: addLinksExtern 361 362 /* previous and next revision */ 363 $changelog = new PageChangeLog($ID); 364 $pagerevs = $changelog->getRevisions(0, $conf['recent'] + 1); 365 $prevrev = false; 366 $nextrev = false; 367 if (!$REV) { 368 // latest revision, previous rev is on top in array 369 $prevrev = 0; 370 } else { 371 // other revision 372 $currentrev = array_search($REV, $pagerevs); 373 if ($currentrev !== false) { 374 $prevrev = $currentrev + 1; 375 $nextrev = $currentrev - 1; 376 } 377 } 378 if ($prevrev !== false && $prevrev > -1 && page_exists($ID, $pagerevs[$prevrev])) { 379 /* previous revision*/ 380 $wikipage->addVersionPrevious($pagerevs[$prevrev]); 381 } 382 if ($nextrev !== false && $nextrev > -1 && page_exists($ID, $pagerevs[$nextrev])) { 383 /* next revision*/ 384 $wikipage->addVersionNext($pagerevs[$nextrev]); 385 } 386 387 /* latest revision */ 388 if ($REV) { 389 $wikipage->addVersionLatest(); 390 } 391 // TODO: topics 392 /* has_container */ 393 if ($INFO['namespace']) { 394 $wikipage->addContainer($INFO['namespace']); 395 } 396 /* has_space */ 397 if ($this->getConf('owners')) { 398 $wikipage->addSite($this->getConf('owners')); 399 } 400 // TODO: dc:contributor / has_modifier 401 // TODO: attachment (e.g. pictures in that dwns) 402 403 // add wiki page to exporter 404 $exporter->addObject($wikipage); 405 //if ($INFO['editor'] && $this->getConf('userns')) $exporter->addObject($pageuser); 406 407 return $exporter; 408 } 409 410 private function getDokuUrl($url = null) 411 { 412 return getAbsUrl($url); 413 } 414 415 public function isRdfXmlRequest(): bool 416 { 417 // get accepted types 418 $http_accept = trim($_SERVER['HTTP_ACCEPT']); 419 420 // save accepted types in array 421 $accepted = explode(',', $http_accept); 422 423 if ($this->getConf('softck') && strpos($_SERVER['HTTP_ACCEPT'], 'application/rdf+xml') !== false) { 424 return true; 425 } 426 427 if (count($accepted) > 0) { 428 // hard check, only serve RDF if it is requested first or equal to first type 429 430 // extract accepting ratio 431 $test_accept = array(); 432 foreach ($accepted as $format) { 433 $formatspec = explode(';', $format); 434 $k = trim($formatspec[0]); 435 if (count($formatspec) === 2) { 436 $test_accept[$k] = trim($formatspec[1]); 437 } else { 438 $test_accept[$k] = 'q=1.0'; 439 } 440 } 441 442 // sort by ratio 443 arsort($test_accept); 444 $accepted_order = array_keys($test_accept); 445 446 if ($accepted_order[0] === 'application/rdf+xml' || (array_key_exists( 447 'application/rdf+xml', 448 $test_accept 449 ) && $test_accept['application/rdf+xml'] === 'q=1.0')) { 450 return true; 451 } 452 } 453 454 // print_r($accepted_order);print_r($test_accept);die(); 455 456 return false; 457 } 458 459 /** 460 */ 461 public function createRdfLink($event = null, $param = null) 462 { 463 global $ID, $INFO, $conf; 464 465 // Test for hidden pages 466 467 if (isHiddenPage($ID)) { 468 return false; 469 } 470 471 // Get type of SIOC content 472 473 $sioc_type = $this->getContenttype(); 474 475 // Test for valid types 476 477 if (!(($sioc_type === 'post' && $INFO['exists']) || $sioc_type === 'user' || $sioc_type === 'container')) { 478 return false; 479 } 480 481 // Test for permission 482 483 if (!$INFO['perm']) { 484 // not enough rights to see the wiki page 485 return false; 486 } 487 488 $userinfo = getDwUserInfo($ID, $this); 489 490 // Create attributes for meta link 491 492 $metalink['type'] = 'application/rdf+xml'; 493 $metalink['rel'] = 'meta'; 494 495 switch ($sioc_type) { 496 case 'container': 497 $title = htmlentities( 498 "Container '" . ($INFO['meta']['title'] ?? $ID) . "' (SIOC document as RDF/XML)" 499 ); 500 $queryAttr = array('type' => 'container'); 501 break; 502 503 case 'user': 504 $title = htmlentities($userinfo['name']); 505 $queryAttr = array('type' => 'user'); 506 break; 507 508 case 'post': 509 default: 510 $title = htmlentities($INFO['meta']['title'] ?? $ID); 511 $queryAttr = array('type' => 'post'); 512 if (isset($_GET['rev']) && $_GET['rev'] === (int)$_GET['rev']) { 513 $queryAttr['rev'] = $_GET['rev']; 514 } 515 break; 516 } 517 518 $metalink['title'] = $title; 519 $metalink['href'] = normalizeUri(getAbsUrl(exportlink($ID, 'siocxml', $queryAttr, false, '&'))); 520 521 if ($event !== null) { 522 $event->data['link'][] = $metalink; 523 524 // set canocial link for type URIs to prevent indexing double content 525 if ($_GET['type'] ?? "") { 526 $event->data['link'][] = array('rel' => 'canonical', 'href' => getAbsUrl(wl($ID))); 527 } 528 } 529 530 return $metalink; 531 } 532} 533 534// TODO cleanup and just have this unconditionally 535if (!function_exists('getAbsUrl')) { 536 /** 537 * @param $url 538 * @return string 539 * @deprecated cleanup, use build-in function 540 */ 541 function getAbsUrl($url = null): string 542 { 543 if ($url === null) { 544 $url = DOKU_BASE; 545 } 546 return str_replace(DOKU_BASE, DOKU_URL, $url); 547 } 548} 549 550if (!function_exists('getDwUserEmail')) { 551 /** 552 * @param $user 553 * @return string 554 * @deprecated not used, will be removed 555 */ 556 function getDwUserEmail($user): string 557 { 558 global $auth; 559 if ($info = $auth->getUserData($user)) { 560 return $info['mail']; 561 } else { 562 return false; 563 } 564 } 565} 566 567if (!function_exists('getDwUserInfo')) { 568 /** 569 * @param $id 570 * @param $pobj 571 * @param $key 572 * @return array|false 573 * @deprecated cleanup, use build-in function 574 */ 575 function getDwUserInfo($id, $pobj, $key = null) 576 { 577 global $auth, $conf; 578 579 if (!$pobj->getConf('userns')) { 580 return false; 581 } 582 583 // get user id 584 $userid = str_replace(cleanID($pobj->getConf('userns')) . ($conf['useslash'] ? '/' : ':'), '', $id); 585 586 if ($info = $auth->getUserData($userid)) { 587 if ($key) { 588 return $info['key']; 589 } else { 590 return $info; 591 } 592 } else { 593 return false; 594 } 595 } 596} 597 598// sort query attributes by name 599if (!function_exists('normalizeUri')) { 600 /** 601 * @param $uri 602 * @return string 603 * @deprecated cleanup, use build-in function 604 */ 605 function normalizeUri($uri): string 606 { 607 // part URI 608 $parts = explode('?', $uri); 609 610 // part query 611 if (isset($parts[1])) { 612 $query = $parts[1]; 613 614 // test separator 615 $sep = '&'; 616 if (strpos($query, '&') !== false) { 617 $sep = '&'; 618 } 619 $attr = explode($sep, $query); 620 621 sort($attr); 622 623 $parts[1] = implode($sep, $attr); 624 } 625 626 return implode('?', $parts); 627 } 628} 629 630