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, 207 normalizeUri($exporter->siocURL('container', $ID)) 208 ); 209 210 /* container is type=wiki */ 211 if ($ID == $conf['start']) { 212 $wikicontainer->isWiki(); 213 } 214 /* sioc:name */ 215 if ($INFO['exists']) { 216 $wikicontainer->addTitle($INFO['meta']['title']); 217 } 218 /* has_parent */ 219 if ($INFO['namespace']) { 220 $wikicontainer->addParent($INFO['namespace']); 221 } 222 223 // search next level entries (posts, sub containers) in container 224 require_once(DOKU_INC . 'inc/search.php'); 225 $dir = utf8_encodeFN(str_replace(':', '/', $ID)); 226 $entries = array(); 227 $posts = array(); 228 $containers = array(); 229 search($entries, $conf['datadir'], 'search_index', array('ns' => $ID), $dir); 230 foreach ($entries as $entry) { 231 if ($entry['type'] === 'f') { 232 // wikisite 233 $posts[] = $entry; 234 } elseif ($entry['type'] === 'd') { 235 // sub container 236 $containers[] = $entry; 237 } 238 } 239 240 // without sub content it can't be a container (so it does not exist as a container) 241 if (count($posts) + count($containers) == 0) { 242 $this->exit("HTTP/1.0 404 Not Found"); 243 } 244 245 if (count($posts) > 0) { 246 $wikicontainer->addArticles($posts); 247 } 248 if (count($containers) > 0) { 249 $wikicontainer->addContainers($containers); 250 } 251 252 //print_r($containers);die(); 253 254 // add container to exporter 255 $exporter->addObject($wikicontainer); 256 257 return $exporter; 258 } 259 260 /* -- private helpers --------------------------------------------------- */ 261 262 private function exportUsercontent($exporter) 263 { 264 global $ID; 265 266 // get user info 267 $userinfo = getDwUserInfo($ID, $this); 268 269 // no userinfo means there is no user space or user does not exists 270 if ($userinfo === false) { 271 $this->exit("HTTP/1.0 404 Not Found"); 272 } 273 274 $exporter->setParameters( 275 'Account: ' . $userinfo['name'], 276 getAbsUrl(), 277 getAbsUrl() . 'doku.php?', 278 'utf-8', 279 $this->agentlink 280 ); 281 // create user object 282 //print_r($userinfo); die(); 283 // $id, $url, $userid, $name, $email 284 $wikiuser = new SIOCDokuWikiUser( 285 $ID, 286 normalizeUri($exporter->siocURL('user', $ID)), 287 $userinfo['user'], 288 $userinfo['name'], 289 $userinfo['mail'] 290 ); 291 /* TODO: avatar (using Gravatar) */ /* TODO: creator_of */ 292 // add user to exporter 293 $exporter->addObject($wikiuser); 294 295 //print_r(headers_list());die(); 296 return $exporter; 297 } 298 299 private function exportPostcontent($exporter) 300 { 301 global $ID, $INFO, $REV, $conf; 302 303 $exporter->setParameters( 304 $INFO['meta']['title'] . ($REV ? ' (rev ' . $REV . ')' : ''), 305 $this->getDokuUrl(), 306 $this->getDokuUrl() . 'doku.php?', 307 'utf-8', 308 $this->agentlink 309 ); 310 311 // create user object 312 $dwuserpage_id = cleanID($this->getConf('userns')) . ($conf['useslash'] ? '/' : ':') . $INFO['editor']; 313 // create wiki page object 314 $wikipage = new SIOCDokuWikiArticle( 315 $ID, // id 316 normalizeUri( 317 $exporter->siocURL( 318 'post', 319 $ID . ($REV ? $exporter->_urlseparator . 'rev' . $exporter->_urlequal . $REV : '') 320 ) 321 ), // url 322 $INFO['meta']['title'] . ($REV ? ' (rev ' . $REV . ')' : ''), // subject 323 rawWiki($ID, $REV) // body (content) 324 ); 325 /* encoded content */ 326 $wikipage->addContentEncoded(p_cached_output(wikiFN($ID, $REV), 'xhtml')); 327 /* created */ 328 if (isset($INFO['meta']['date']['created'])) { 329 $wikipage->addCreated(date('c', $INFO['meta']['date']['created'])); 330 } 331 /* or modified */ 332 if (isset($INFO['meta']['date']['modified'])) { 333 $wikipage->addModified(date('c', $INFO['meta']['date']['modified'])); 334 } 335 /* creator/modifier */ 336 if ($INFO['editor'] && $this->getConf('userns')) { 337 $wikipage->addCreator(array('foaf:maker' => '#' . $INFO['editor'], 'sioc:modifier' => $dwuserpage_id)); 338 } 339 /* is creator */ 340 if (isset($INFO['meta']['date']['created'])) { 341 $wikipage->isCreator(); 342 } 343 /* intern wiki links */ 344 $wikipage->addLinks($INFO['meta']['relation']['references']); 345 346 // contributors - only for last revision b/c of wrong meta data for older revisions 347 if (!$REV && $this->getConf('userns') && isset($INFO['meta']['contributor'])) { 348 $cont_temp = array(); 349 $cont_ns = $this->getConf('userns') . ($conf['useslash'] ? '/' : ':'); 350 foreach ($INFO['meta']['contributor'] as $cont_id => $cont_name) { 351 $cont_temp[$cont_ns . $cont_id] = $cont_name; 352 } 353 $wikipage->addContributors($cont_temp); 354 } 355 356 // backlinks - only for last revision 357 if (!$REV) { 358 require_once(DOKU_INC . 'inc/fulltext.php'); 359 $backlinks = ft_backlinks($ID); 360 if (count($backlinks) > 0) { 361 $wikipage->addBacklinks($backlinks); 362 } 363 } 364 365 // TODO: addLinksExtern 366 367 /* previous and next revision */ 368 $changelog = new PageChangeLog($ID); 369 $pagerevs = $changelog->getRevisions(0, $conf['recent'] + 1); 370 $prevrev = false; 371 $nextrev = false; 372 if (!$REV) { 373 // latest revision, previous rev is on top in array 374 $prevrev = 0; 375 } else { 376 // other revision 377 $currentrev = array_search($REV, $pagerevs); 378 if ($currentrev !== false) { 379 $prevrev = $currentrev + 1; 380 $nextrev = $currentrev - 1; 381 } 382 } 383 if ($prevrev !== false && $prevrev > -1 && page_exists($ID, $pagerevs[$prevrev])) { 384 /* previous revision*/ 385 $wikipage->addVersionPrevious($pagerevs[$prevrev]); 386 } 387 if ($nextrev !== false && $nextrev > -1 && page_exists($ID, $pagerevs[$nextrev])) { 388 /* next revision*/ 389 $wikipage->addVersionNext($pagerevs[$nextrev]); 390 } 391 392 /* latest revision */ 393 if ($REV) { 394 $wikipage->addVersionLatest(); 395 } 396 // TODO: topics 397 /* has_container */ 398 if ($INFO['namespace']) { 399 $wikipage->addContainer($INFO['namespace']); 400 } 401 /* has_space */ 402 if ($this->getConf('owners')) { 403 $wikipage->addSite($this->getConf('owners')); 404 } 405 // TODO: dc:contributor / has_modifier 406 // TODO: attachment (e.g. pictures in that dwns) 407 408 // add wiki page to exporter 409 $exporter->addObject($wikipage); 410 //if ($INFO['editor'] && $this->getConf('userns')) $exporter->addObject($pageuser); 411 412 return $exporter; 413 } 414 415 private function getDokuUrl($url = null) 416 { 417 return getAbsUrl($url); 418 } 419 420 public function isRdfXmlRequest(): bool 421 { 422 if (!isset($_SERVER['HTTP_ACCEPT'])) { 423 return false; 424 } 425 426 // get accepted types 427 $http_accept = trim($_SERVER['HTTP_ACCEPT']); 428 429 // save accepted types in array 430 $accepted = explode(',', $http_accept); 431 432 if ($this->getConf('softck') && strpos($_SERVER['HTTP_ACCEPT'], 'application/rdf+xml') !== false) { 433 return true; 434 } 435 436 if (count($accepted) > 0) { 437 // hard check, only serve RDF if it is requested first or equal to first type 438 439 // extract accepting ratio 440 $test_accept = array(); 441 foreach ($accepted as $format) { 442 $formatspec = explode(';', $format); 443 $k = trim($formatspec[0]); 444 if (count($formatspec) === 2) { 445 $test_accept[$k] = trim($formatspec[1]); 446 } else { 447 $test_accept[$k] = 'q=1.0'; 448 } 449 } 450 451 // sort by ratio 452 arsort($test_accept); 453 $accepted_order = array_keys($test_accept); 454 455 if ($accepted_order[0] === 'application/rdf+xml' || (array_key_exists( 456 'application/rdf+xml', 457 $test_accept 458 ) && $test_accept['application/rdf+xml'] === 'q=1.0')) { 459 return true; 460 } 461 } 462 463 // print_r($accepted_order);print_r($test_accept);die(); 464 465 return false; 466 } 467 468 /** 469 */ 470 public function createRdfLink($event = null, $param = null) 471 { 472 global $ID, $INFO, $conf; 473 474 // Test for hidden pages 475 476 if (isHiddenPage($ID)) { 477 return false; 478 } 479 480 // Get type of SIOC content 481 482 $sioc_type = $this->getContenttype(); 483 484 // Test for valid types 485 486 if (!(($sioc_type === 'post' && $INFO['exists']) || $sioc_type === 'user' || $sioc_type === 'container')) { 487 return false; 488 } 489 490 // Test for permission 491 492 if (!$INFO['perm']) { 493 // not enough rights to see the wiki page 494 return false; 495 } 496 497 $userinfo = getDwUserInfo($ID, $this); 498 499 // Create attributes for meta link 500 501 $metalink['type'] = 'application/rdf+xml'; 502 $metalink['rel'] = 'meta'; 503 504 switch ($sioc_type) { 505 case 'container': 506 $title = htmlentities( 507 "Container '" . ($INFO['meta']['title'] ?? $ID) . "' (SIOC document as RDF/XML)" 508 ); 509 $queryAttr = array('type' => 'container'); 510 break; 511 512 case 'user': 513 $title = htmlentities($userinfo['name']); 514 $queryAttr = array('type' => 'user'); 515 break; 516 517 case 'post': 518 default: 519 $title = htmlentities($INFO['meta']['title'] ?? $ID); 520 $queryAttr = array('type' => 'post'); 521 if (isset($_GET['rev']) && $_GET['rev'] === (int)$_GET['rev']) { 522 $queryAttr['rev'] = $_GET['rev']; 523 } 524 break; 525 } 526 527 $metalink['title'] = $title; 528 $metalink['href'] = normalizeUri(getAbsUrl(exportlink($ID, 'siocxml', $queryAttr, false, '&'))); 529 530 if ($event !== null) { 531 $event->data['link'][] = $metalink; 532 533 // set canocial link for type URIs to prevent indexing double content 534 if ($_GET['type'] ?? "") { 535 $event->data['link'][] = array('rel' => 'canonical', 'href' => getAbsUrl(wl($ID))); 536 } 537 } 538 539 return $metalink; 540 } 541} 542 543// TODO cleanup and just have this unconditionally 544if (!function_exists('getAbsUrl')) { 545 /** 546 * @param $url 547 * @return string 548 * @deprecated cleanup, use build-in function 549 */ 550 function getAbsUrl($url = null): string 551 { 552 if ($url === null) { 553 $url = DOKU_BASE; 554 } 555 return str_replace(DOKU_BASE, DOKU_URL, $url); 556 } 557} 558 559if (!function_exists('getDwUserEmail')) { 560 /** 561 * @param $user 562 * @return string 563 * @deprecated not used, will be removed 564 */ 565 function getDwUserEmail($user): string 566 { 567 global $auth; 568 if ($info = $auth->getUserData($user)) { 569 return $info['mail']; 570 } else { 571 return false; 572 } 573 } 574} 575 576if (!function_exists('getDwUserInfo')) { 577 /** 578 * @param $id 579 * @param $pobj 580 * @param $key 581 * @return array|false 582 * @deprecated cleanup, use build-in function 583 */ 584 function getDwUserInfo($id, $pobj, $key = null) 585 { 586 global $auth, $conf; 587 588 if (!$pobj->getConf('userns')) { 589 return false; 590 } 591 592 // get user id 593 $userid = str_replace(cleanID($pobj->getConf('userns')) . ($conf['useslash'] ? '/' : ':'), '', $id); 594 595 if ($info = $auth->getUserData($userid)) { 596 if ($key) { 597 return $info['key']; 598 } else { 599 return $info; 600 } 601 } else { 602 return false; 603 } 604 } 605} 606 607// sort query attributes by name 608if (!function_exists('normalizeUri')) { 609 /** 610 * @param $uri 611 * @return string 612 * @deprecated cleanup, use build-in function 613 */ 614 function normalizeUri($uri): string 615 { 616 // part URI 617 $parts = explode('?', $uri); 618 619 // part query 620 if (isset($parts[1])) { 621 $query = $parts[1]; 622 623 // test separator 624 $sep = '&'; 625 if (strpos($query, '&') !== false) { 626 $sep = '&'; 627 } 628 $attr = explode($sep, $query); 629 630 sort($attr); 631 632 $parts[1] = implode($sep, $attr); 633 } 634 635 return implode('?', $parts); 636 } 637} 638