1<?php 2/** 3 * DokuWiki Plugin bibtex4dw (BibTeX Renderer Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Till Biskup <till@till-biskup.de> 7 * @version 0.4.0 8 * @date 2023-05-28 9 */ 10 11require_once(DOKU_PLUGIN.'bibtex4dw/lib/bibtexparser.php'); 12 13class bibtexrender_plugin_bibtex4dw { 14 15 /** 16 * Array containing all references to objects of this class that already exist. 17 * 18 * Necessary to synchronize calls from the two different syntax plugins for the same page 19 */ 20 public static $resources = array(); 21 22 /** 23 * Handle to SQLite db 24 */ 25 public static $sqlite = array(); 26 27 /** 28 * Array containing local configuration of the object 29 * 30 * Options can be set by calling setOptions($options = array()) 31 */ 32 private $_conf = array( 33 'sqlite' => false, 34 'file' => array(), 35 'citetype' => '', 36 'sort' => false, 37 'formatstring' => array(), 38 ); 39 40 private $_langStrings = array( 41 'pageabbrev', 42 'pagesabbrev', 43 'chapterabbrev', 44 'editorabbrev', 45 'mastersthesis', 46 'phdthesis', 47 'techreport', 48 'unpublished' 49 ); 50 51 /** 52 * Array containing all references from the BibTeX files loaded 53 */ 54 private $_bibtex_references = array(); 55 56 /** 57 * Array containing all keys from the BibTeX records in $_bibtex_references 58 */ 59 private $_bibtex_keys = array(); 60 61 /** 62 * Array containing all keys already cited together with their number 63 */ 64 private $_bibtex_keysCited = array(); 65 66 /** 67 * Array containing all keys given with "nocite" together with their number 68 */ 69 private $_bibtex_keysNotCited = array(); 70 71 /** 72 * Number of the currently highest cite key if style "numeric" is used 73 */ 74 private $_currentKeyNumber = 0; 75 76 /** 77 * Initializes the class 78 * 79 * As it loads the configuration options set e.g. via the config manager via the admin 80 * interface of dokuwiki, the plugin can be configured such that only the final 81 * <bibtex bibliography></bibtex> pattern is necessary to print the actual bibliography. 82 */ 83 function __construct() { 84 // Use trick to access configuration and language stuff from the actual bibtex plugin 85 // with the builtin methods of DW 86 require_once(DOKU_PLUGIN.'bibtex4dw/syntax/bibtex.php'); 87 $this->plugin = new syntax_plugin_bibtex4dw_bibtex(); 88 89 // Transfer config settings from plugin config (via config manager) to local config 90 // Note: Only those settings that can be changed by this class need to be transferred. 91 // Therefore it is not necessary to transfer the format strings for the entries. 92 $this->_conf['sqlite'] = $this->plugin->getConf('sqlite'); 93 $this->_conf['file'] = explode(';',$this->plugin->getConf('file')); 94 $this->_conf['pdfdir'] = explode(';',$this->plugin->getConf('pdfdir')); 95 $this->_conf['citetype'] = $this->plugin->getConf('citetype'); 96 97 // In case we shall use SQLite 98 if ($this->_conf['sqlite']) { 99 $this->sqlite = plugin_load('helper', 'sqlite'); 100 if(!$this->sqlite){ 101 msg('You asked for using the sqlite plugin but it is not installed. Please install it', -1); 102 return; 103 } 104 // initialize the database connection 105 if(!$this->sqlite->init('bibtex4dw', DOKU_PLUGIN.'bibtex4dw/db/')){ 106 return; 107 } 108 } else { 109 // If there are files to load, load and parse them 110 if (array_key_exists('file', $this->_conf)) { 111 $this->_parseBibtexFile(); 112 } 113 } 114 } 115 116 /** 117 * Gets instance of the class by id 118 * 119 * @param pageid 120 */ 121 public static function getResource($id = NULL) { 122 // exit if given parameters not sufficient. 123 if (is_null($id)) { 124 return null; 125 } 126 if (!array_key_exists($id, self::$resources)) { 127 $x = new bibtexrender_plugin_bibtex4dw($id); 128 self::$resources[$id] = $x; 129 } 130 // return the desired object or null in case of error 131 return self::$resources[$id]; 132 } 133 134 /** 135 * Set options of object 136 * 137 * @param options 138 */ 139 public function setOptions($options) { 140 // Clear nocite array if it already exists and is not empty. 141 // This is necessary for the "furtherreading" bibliographies, so that you 142 // can have more than one of these bibliographies that are independent. 143 if (isset($this->_bibtex_keysNotCited) && (count($this->_bibtex_keysNotCited))) { 144 $this->_bibtex_keysNotCited = array(); 145 } 146 // Handle options, convert to $_conf if possible 147 foreach($options as $optkey => $optval) { 148 if ('nocite' == $optkey) { 149 // if $optval is an array (i.e. more than one "nocite" was given) 150 if (is_array($optval)) { 151 $optval = implode(',',$optval); 152 } 153 $bibkeys = explode(',',$optval); 154 foreach ($bibkeys as $bibkey) { 155 $this->_bibtex_keysNotCited[$bibkey] = 0; 156 } 157 } 158 if (array_key_exists($optkey,$this->_conf)) { 159 // For file, allow multiple entries 160 if ('file' == $optkey) { 161 if (is_array($optval) && (count($optval) > 1)) { 162 $this->_conf[$optkey] = $optval; 163 } else { 164 $this->_conf[$optkey][] = $optval[0]; 165 } 166 // In all other cases, the last entry of a type wins 167 } else { 168 $this->_conf[$optkey] = $optval[count($optval)-1]; 169 } 170 } 171 } 172 173 // Set sort option depending on citetype 174 if (!array_key_exists('sort',$options)) { 175 switch ($this->_conf['citetype']) { 176 case 'apa': 177 $this->_conf['sort'] = 'true'; 178 break; 179 case 'alpha': 180 $this->_conf['sort'] = 'true'; 181 break; 182 case 'authordate': 183 $this->_conf['sort'] = 'true'; 184 break; 185 case 'numeric': 186 $this->_conf['sort'] = 'false'; 187 break; 188 default: 189 $this->_conf['sort'] = 'false'; 190 break; 191 } 192 } 193 194 } 195 196 /** 197 * Internal function parsing the contents of the BibTeX file 198 * 199 * The result will be stored in $this->_bibtex_references 200 */ 201 private function _parseBibtexFile() { 202 $bibtex = ''; 203 // Load all files and concatenate their contents 204 foreach($this->_conf['file'] as $file) { 205 $bibtex .= $this->_loadBibtexFile($file, 'page'); 206 } 207 $this->_parser = new bibtexparser_plugin_bibtex4dw(); 208 $this->_parser->loadString($bibtex); 209 $stat = $this->_parser->parseBibliography(); 210 if ( !$stat ) { 211 return $stat; 212 } 213 //$this->_bibtex_references = $this->_parser->data; 214 $this->_bibtex_references = $this->_parser->entries; 215 216 foreach($this->_bibtex_references as $refno => $ref) { 217 if (is_array($ref) && array_key_exists('cite', $ref)) { 218 $this->_bibtex_keys[$ref['cite']] = $refno; 219 } 220 } 221 } 222 223 /** 224 * Internal function adding the content to the SQLite database 225 */ 226 public function addBibtexToSQLite($bibtex,$ID) { 227 if (!$this->_conf['sqlite']) { 228 return; 229 } 230 if (!in_array(':'.$ID, $this->_conf['file'])) { 231 msg("Current page (:$ID) not configured to be a BibTeX DB, hence ignoring. 232 Change in config if this is not intended."); 233 return; 234 } 235 $this->_parser = new bibtexparser_plugin_bibtex4dw(); 236 $this->_parser->loadString($bibtex); 237 $this->_parser->sqlite = $this->sqlite; 238 $stat = $this->_parser->parseBibliography($sqlite=true); 239 240 if ( !$stat ) { 241 msg('Some problems with parsing BIBTeX code',-1); 242 } 243 244 if ( ($this->_parser->warnings['warning']) && (count($this->_parser->warnings['warning']))) { 245 foreach($this->_parser->warnings as $parserWarning) { 246 msg($this->_parser->warnings[$parserWarning]['warning'],'2'); 247 } 248 } 249 } 250 251 /** 252 * Prints the reference corresponding to the given bibtex key 253 * 254 * The format of the reference can be freely configured using the 255 * dokuwiki configuration interface (see files in conf dir) 256 * 257 * @param string BibTeX key of the reference 258 * @return string (HTML) formatted BibTeX reference 259 */ 260 public function printReference($bibtex_key) { 261 global $INFO; 262 263 if ($this->_conf['sqlite']) { 264 $this->_parser = new bibtexparser_plugin_bibtex4dw(); 265 $this->_parser->sqlite = $this->sqlite; 266 $rawBibtexEntry = $this->sqlite->res2arr($this->sqlite->query("SELECT entry FROM bibtex WHERE key=?",$bibtex_key)); 267 $this->_parser->loadString($rawBibtexEntry[0]['entry']); 268 $stat = $this->_parser->parse(); 269 if ( !$stat ) { 270 return $stat; 271 } 272 $ref = $this->_parser->data[0]; 273 } else { 274 //$ref = $this->_bibtex_references[$this->_bibtex_keys[$bibtex_key]]; 275 $rawBibtexEntry = $this->_bibtex_references[$bibtex_key]; 276 $this->_parser->loadString($rawBibtexEntry); 277 $stat = $this->_parser->parse(); 278 if ( !$stat ) { 279 return $stat; 280 } 281 $ref = $this->_parser->data[0]; 282 } 283 if (empty($ref)) { 284 return; 285 } 286 // Variant of $ref with normalized (i.e., all uppercase) field names 287 $normalizedRef = []; 288 foreach ($ref as $key => $value) { 289 $normalizedRef[strtoupper($key)] = $value; 290 } 291 // Get format string from plugin config 292 $formatstring = $this->plugin->getConf('fmtstr_'.$normalizedRef['ENTRYTYPE']); 293 // Replace each language string ($this->_langStrings) pattern '@placeholder@' with respective value 294 foreach ($this->_langStrings as $lang) { 295 $formatstring = str_replace('@'.strtolower($lang).'@', $this->plugin->getLang($lang), $formatstring); 296 } 297 // Replace each field pattern '{...@FIELDNAME@...}' with respective value from bib data 298 preg_match_all("#\{([^@]*)@([A-Z]+)@([^@]*)\}#U", $formatstring, $fieldsToBeReplaced, PREG_SET_ORDER); 299 foreach ($fieldsToBeReplaced as $matchPair) { 300 $partOfFormatstring = $matchPair[0]; 301 $priorToName = $matchPair[1]; 302 $fieldName = $matchPair[2]; 303 $afterName = $matchPair[3]; 304 if (empty($normalizedRef[$fieldName])) { 305 $formatstring = str_replace($partOfFormatstring, "", $formatstring); 306 continue; 307 } 308 $formattedPart = $priorToName; 309 $formattedPart .= $normalizedRef[$fieldName]; 310 $formattedPart .= $afterName; 311 $formatstring = str_replace($partOfFormatstring, $formattedPart, $formatstring); 312 } 313 // Handle PDF files 314 // Check whether we have a directory for PDF files 315 if (array_key_exists('pdfdir',$this->_conf)) { 316 // Check whether we are logged in and have permissions to access the PDFs 317 if ((auth_quickaclcheck($this->_conf['pdfdir'][0]) >= AUTH_READ) && 318 array_key_exists('name',$INFO['userinfo'])) { 319 // do sth. 320 $pdffilename = mediaFN($this->_conf['pdfdir'][0]) . "/" . $bibtex_key . ".pdf"; 321 if (file_exists($pdffilename)) { 322 resolve_mediaid($this->_conf['pdfdir'][0], $pdflinkname, $exists); 323 $formatstring .= ' <a href="' . 324 ml($pdflinkname) . "/" . $bibtex_key . ".pdf" . '">PDF</a>'; 325 } 326 } 327 } 328 329 return $formatstring; 330 } 331 332 /** 333 * Prints the whole bibliography 334 * 335 * @param string substate (bibliography, furtherreading) 336 * @return string (HTML) formatted bibliography 337 */ 338 public function printBibliography($substate) { 339 switch ($substate) { 340 case 'bibliography': 341 if (!isset($this->_bibtex_keysCited) || empty($this->_bibtex_keysCited)) { 342 return; 343 } 344 // If there are nocite entries 345 if (isset($this->_bibtex_keysNotCited) && !empty($this->_bibtex_keysNotCited)) { 346 foreach ($this->_bibtex_keysNotCited as $key => $no) { 347 if (!array_key_exists($key,$this->_bibtex_keysCited)) { 348 $this->_bibtex_keysCited[$key] = ++$this->_currentKeyNumber; 349 } 350 } 351 } 352 if ('true' == $this->_conf['sort'] && 'numeric' != $this->_conf['citetype']) { 353 $citedKeys = array(); 354 foreach ($this->_bibtex_keysCited as $key => $no) { 355 if ($this->_conf['sqlite']) { 356 $this->_parser = new bibtexparser_plugin_bibtex4dw(); 357 $this->_parser->sqlite = $this->sqlite; 358 $rawBibtexEntry = $this->sqlite->res2arr($this->sqlite->query("SELECT entry FROM bibtex WHERE key=?",$key)); 359 $this->_parser->loadString($rawBibtexEntry[0]['entry']); 360 $stat = $this->_parser->parse(); 361 if ( !$stat ) { 362 return $stat; 363 } 364 $citedKeys[$key] = $this->_parser->data[0]['authoryear']; 365 } else { 366 $citedKeys[$key] = $this->_bibtex_references[$this->_bibtex_keys[$key]]['authoryear']; 367 } 368 } 369 asort($citedKeys); 370 } else { 371 $citedKeys = $this->_bibtex_keysCited; 372 } 373 if ('authordate' == $this->_conf['citetype']) { 374 $html = $this->_printReferencesAsUnorderedList($citedKeys); 375 } else { 376 $html = $this->_printReferencesAsDefinitionlist($citedKeys); 377 } 378 return $html; 379 case 'furtherreading': 380 if (!isset($this->_bibtex_keysNotCited) || empty($this->_bibtex_keysNotCited)) { 381 return; 382 } 383 $this->_currentKeyNumber = 0; 384 if ('true' == $this->_conf['sort']) { 385 $notcitedKeys = array(); 386 foreach ($this->_bibtex_keysNotCited as $key => $no) { 387 if ($this->_conf['sqlite']) { 388 $this->_parser = new bibtexparser_plugin_bibtex4dw(); 389 $this->_parser->sqlite = $this->sqlite; 390 $rawBibtexEntry = $this->sqlite->res2arr($this->sqlite->query("SELECT entry FROM bibtex WHERE key=?",$key)); 391 $this->_parser->loadString($rawBibtexEntry[0]['entry']); 392 $stat = $this->_parser->parse(); 393 if ( !$stat ) { 394 return $stat; 395 } 396 $notcitedKeys[$key] = $this->_parser->data[0]['authoryear']; 397 } else { 398 $notcitedKeys[$key] = $this->_bibtex_references[$this->_bibtex_keys[$key]]['authoryear']; 399 } 400 } 401 asort($notcitedKeys); 402 } else { 403 $notcitedKeys = $this->_bibtex_keysNotCited; 404 } 405 if ('authordate' == $this->_conf['citetype']) { 406 $html = $this->_printReferencesAsUnorderedList($notcitedKeys); 407 } else { 408 $html = $this->_printReferencesAsDefinitionlist($notcitedKeys); 409 } 410 return $html; 411 } 412 } 413 414 /** 415 * Print references as unordered list 416 * 417 * Currently used only for "authordate" citation style 418 * 419 * @param array List of keys bibliography should be generated for 420 * @return string rendered HTML of bibliography 421 */ 422 function _printReferencesAsUnorderedList($citedKeys) { 423 $html = '<ul class="bibtex_references">' . DOKU_LF; 424 foreach ($citedKeys as $key => $no) { 425 if ($this->keyExists($key)) { 426 $html .= '<li><div class="li" name="ref__' . $key . '" id="ref__'. $key . '">'; 427 $html .= $this->printReference($key); 428 $html .= '</div></li>' . DOKU_LF; 429 } else { 430 msg("BibTeX key '$key' could not be found. Possible typo?"); 431 } 432 } 433 $html .= '</ul>'; 434 return $html; 435 } 436 437 /** 438 * Print references as definitionlist 439 * 440 * Currently used for all citation styles except "authordate" 441 * 442 * @param array List of keys bibliography should be generated for 443 * @return string rendered HTML of bibliography 444 */ 445 function _printReferencesAsDefinitionlist($citedKeys) { 446 $html = '<dl class="bibtex_references">' . DOKU_LF; 447 foreach ($citedKeys as $key => $no) { 448 if ($this->keyExists($key)) { 449 $html .= '<dt>['; 450 $html .= $this->printCitekey($key); 451 $html .= ']</dt>' . DOKU_LF; 452 $html .= '<dd>'; 453 $html .= $this->printReference($key); 454 $html .= '</dd>' . DOKU_LF; 455 } else { 456 msg("BibTeX key '$key' could not be found. Possible typo?"); 457 } 458 } 459 $html .= '</dl>'; 460 return $html; 461 } 462 463 /** 464 * Return the cite key of the given reference for using in the text 465 * The Output depends on the configuration via the variable 466 * $this->_conf['citetype'] 467 * If the citetype is unknown, the bibtex key is returned 468 * 469 * @param string bibtex key of the reference 470 * @return string cite key of the reference (according to the citetype set) 471 */ 472 public function printCitekey($bibtex_key) { 473 if (!array_key_exists($bibtex_key,$this->_bibtex_keysCited)) { 474 $this->_currentKeyNumber++; 475 $this->_bibtex_keysCited[$bibtex_key] = $this->_currentKeyNumber; 476 } 477 if ($this->_conf['sqlite']) { 478 $this->_parser = new bibtexparser_plugin_bibtex4dw(); 479 $rawBibtexEntry = $this->sqlite->res2arr($this->sqlite->query("SELECT entry FROM bibtex WHERE key=?",$bibtex_key)); 480 if (empty($rawBibtexEntry)) { 481 return $bibtex_key; 482 } 483 $this->_parser->loadString($rawBibtexEntry[0]['entry']); 484 $stat = $this->_parser->parse(); 485 if ( !$stat ) { 486 return $stat; 487 } 488 $ref = $this->_parser->data[0]; 489 } else { 490 // Check whether key exists 491 if (empty($this->_bibtex_references[$bibtex_key])) { 492 return $bibtex_key; 493 } 494 $ref = $this->_bibtex_references[$this->_bibtex_keys[$bibtex_key]]; 495 } 496 switch ($this->_conf['citetype']) { 497 case 'apa': 498 $bibtex_key = $ref['authors'][0]['last']; 499 if ($ref['authors'][0]['last'] == '') { 500 $bibtex_key = $ref['editors'][0]['last']; 501 } 502 $bibtex_key .= $ref['year']; 503 break; 504 case 'alpha': 505 $bibtex_key = substr($ref['authors'][0]['last'],0,3); 506 if ($ref['authors'][0]['last'] == '') { 507 $bibtex_key = substr($ref['editors'][0]['last'],0,3); 508 } 509 $bibtex_key .= substr($ref['year'],2,2); 510 break; 511 case 'authordate': 512 $bibtex_key = $ref['authors'][0]['last'] . ", "; 513 if ($ref['authors'][0]['last'] == '') { 514 $bibtex_key = $ref['editors'][0]['last'] . ", "; 515 } 516 $bibtex_key .= $ref['year']; 517 break; 518 case 'numeric': 519 $bibtex_key = $this->_bibtex_keysCited[$bibtex_key]; 520 break; 521 // If no known citation style is given - however, that should not happen 522 default: 523 $bibtex_key = $this->_bibtex_keysCited[$bibtex_key]; 524 break; 525 } 526 return $bibtex_key; 527 } 528 529 /** 530 * Check if given key exists in currently used BibTeX database 531 * 532 * @param string bibtex key of the reference 533 * @return Boolean value 534 */ 535 function keyExists($bibkey) { 536 if ($this->_conf['sqlite']) { 537 $rawBibtexEntry = $this->sqlite->res2arr($this->sqlite->query("SELECT entry FROM bibtex WHERE key=?",$bibkey)); 538 return (!empty($rawBibtexEntry)); 539 } else { 540 return (!empty($this->_bibtex_references[$bibkey])); 541 } 542 } 543 544 /** 545 * Debug function to output the raw contents of the BibTeX file 546 * 547 * @return string raw BibTeX code 548 */ 549 function rawOutput() { 550 $bibtex = ''; 551 // Load all files and concatenate their contents 552 foreach($this->_conf['file'] as $file) { 553 $bibtex .= $this->_loadBibtexFile($file, 'page'); 554 } 555 return $bibtex; 556 } 557 558 /** 559 * Loads BibTeX code and returns the raw BibTeX as string 560 * 561 * @param string uri BibTeX file (path or dokuwiki page) 562 * @param string kind whether uri is a file or a dokuwiki page 563 * @return string raw BibTeX code 564 */ 565 function _loadBibtexFile($uri, $kind) { 566 global $INFO; 567 568 if ( $kind == 'file' ) { 569 // FIXME: Adjust path - make it configurable 570 return file_get_contents(dirname(__FILE__).'/'.$kind.'/'.$uri); 571 } elseif ($kind == 'page') { 572 $exists = false; 573 resolve_pageid($INFO['namespace'], $uri, $exists); 574 if ( $exists ) { 575 return rawWiki($uri); 576 } 577 } 578 579 return null; 580 } 581 582} 583 584?> 585