1<?php 2 3/** 4 * Plugin RefNotes: Reference database 5 * 6 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 7 * @author Mykola Ostrovskyy <dwpforge@gmail.com> 8 */ 9 10//////////////////////////////////////////////////////////////////////////////////////////////////// 11class refnotes_reference_database { 12 13 private static $instance = NULL; 14 15 private $note; 16 private $key; 17 private $page; 18 private $namespace; 19 private $enabled; 20 21 /** 22 * 23 */ 24 public static function getInstance() { 25 if (self::$instance == NULL) { 26 self::$instance = new refnotes_reference_database(); 27 28 /* Loading has to be separated from construction to prevent infinite recursion */ 29 self::$instance->load(); 30 } 31 32 return self::$instance; 33 } 34 35 /** 36 * Constructor 37 */ 38 public function __construct() { 39 $this->page = array(); 40 $this->namespace = array(); 41 $this->enabled = true; 42 } 43 44 /** 45 * 46 */ 47 private function load() { 48 $this->loadNotesFromConfiguration(); 49 50 if (refnotes_configuration::getSetting('reference-db-enable')) { 51 $this->loadKeys(); 52 $this->loadPages(); 53 $this->loadNamespaces(); 54 } 55 } 56 57 /** 58 * 59 */ 60 private function loadNotesFromConfiguration() { 61 $note = refnotes_configuration::load('notes'); 62 63 foreach ($note as $name => $data) { 64 $this->note[$name] = new refnotes_reference_database_note('{configuration}', $data); 65 } 66 } 67 68 /** 69 * 70 */ 71 private function loadKeys() { 72 $locale = refnotes_localization::getInstance(); 73 foreach ($locale->getByPrefix('dbk') as $key => $text) { 74 $this->key[$this->normalizeKeyText($text)] = $key; 75 } 76 } 77 78 /** 79 * 80 */ 81 public function getKey($text) { 82 $result = ''; 83 $text = $this->normalizeKeyText($text); 84 85 if (in_array($text, $this->key)) { 86 $result = $text; 87 } 88 elseif (array_key_exists($text, $this->key)) { 89 $result = $this->key[$text]; 90 } 91 92 return $result; 93 } 94 95 /** 96 * 97 */ 98 private function normalizeKeyText($text) { 99 return preg_replace('/\s+/', ' ', \dokuwiki\Utf8\PhpString::strtolower(trim($text))); 100 } 101 102 /** 103 * 104 */ 105 private function loadPages() { 106 global $conf; 107 108 if (file_exists($conf['indexdir'] . '/page.idx')) { 109 require_once(DOKU_INC . 'inc/indexer.php'); 110 111 $pageIndex = idx_getIndex('page', ''); 112 $namespace = refnotes_configuration::getSetting('reference-db-namespace'); 113 $namespacePattern = '/^' . trim($namespace, ':') . ':/'; 114 $cache = new refnotes_reference_database_cache(); 115 116 foreach ($pageIndex as $pageId) { 117 $pageId = trim($pageId); 118 119 if ((preg_match($namespacePattern, $pageId) == 1) && file_exists(wikiFN($pageId))) { 120 $this->enabled = false; 121 $this->page[$pageId] = new refnotes_reference_database_page($this, $cache, $pageId); 122 $this->enabled = true; 123 } 124 } 125 126 $cache->save(); 127 } 128 } 129 130 /** 131 * 132 */ 133 private function loadNamespaces() { 134 foreach ($this->page as $pageId => $page) { 135 foreach ($page->getNamespaces() as $ns) { 136 $this->namespace[$ns][] = $pageId; 137 } 138 } 139 } 140 141 /** 142 * 143 */ 144 public function findNote($name) { 145 if (!$this->enabled) { 146 return NULL; 147 } 148 149 $found = array_key_exists($name, $this->note); 150 151 if (!$found) { 152 list($namespace, $temp) = refnotes_namespace::parseName($name); 153 154 if (array_key_exists($namespace, $this->namespace)) { 155 $this->loadNamespaceNotes($namespace); 156 157 $found = array_key_exists($name, $this->note); 158 } 159 } 160 161 return $found ? $this->note[$name] : NULL; 162 } 163 164 /** 165 * 166 */ 167 private function loadNamespaceNotes($namespace) { 168 foreach ($this->namespace[$namespace] as $pageId) { 169 if (array_key_exists($pageId, $this->page)) { 170 $this->enabled = false; 171 $this->note = array_merge($this->note, $this->page[$pageId]->getNotes()); 172 $this->enabled = true; 173 174 unset($this->page[$pageId]); 175 } 176 } 177 178 unset($this->namespace[$namespace]); 179 } 180} 181 182//////////////////////////////////////////////////////////////////////////////////////////////////// 183class refnotes_reference_database_page { 184 185 private $database; 186 private $id; 187 private $fileName; 188 private $namespace; 189 private $note; 190 191 /** 192 * Constructor 193 */ 194 public function __construct($database, $cache, $id) { 195 $this->database = $database; 196 $this->id = $id; 197 $this->fileName = wikiFN($id); 198 $this->namespace = array(); 199 $this->note = array(); 200 201 if ($cache->isCached($this->fileName)) { 202 $this->namespace = $cache->getNamespaces($this->fileName); 203 } 204 else { 205 $this->parse(); 206 207 $cache->update($this->fileName, $this->namespace); 208 } 209 } 210 211 /** 212 * 213 */ 214 private function parse() { 215 $text = io_readWikiPage($this->fileName, $this->id); 216 $call = p_cached_instructions($this->fileName); 217 $calls = count($call); 218 219 for ($c = 0; $c < $calls; $c++) { 220 if ($call[$c][0] == 'table_open') { 221 $c = $this->parseTable($call, $calls, $c, $text); 222 } 223 elseif ($call[$c][0] == 'code') { 224 $this->parseCode($call[$c]); 225 } 226 elseif (($call[$c][0] == 'plugin') && ($call[$c][1][0] == 'data_entry')) { 227 $this->parseDataEntry($call[$c][1][1]); 228 } 229 } 230 } 231 232 /** 233 * 234 */ 235 private function parseTable($call, $calls, $c, $text) { 236 $row = 0; 237 $column = 0; 238 $columns = 0; 239 $valid = true; 240 241 for ( ; $c < $calls; $c++) { 242 switch ($call[$c][0]) { 243 case 'tablerow_open': 244 $column = 0; 245 break; 246 247 case 'tablerow_close': 248 if ($row == 0) { 249 $columns = $column; 250 } 251 else { 252 if ($column != $columns) { 253 $valid = false; 254 break 2; 255 } 256 } 257 $row++; 258 break; 259 260 case 'tablecell_open': 261 case 'tableheader_open': 262 $cellOpen = $call[$c][2]; 263 break; 264 265 case 'tablecell_close': 266 case 'tableheader_close': 267 $table[$row][$column] = trim(substr($text, $cellOpen, $call[$c][2] - $cellOpen), "^| "); 268 $column++; 269 break; 270 271 case 'table_close': 272 break 2; 273 } 274 } 275 276 if ($valid && ($row > 1) && ($columns > 1)) { 277 $this->handleTable($table, $columns, $row); 278 } 279 280 return $c; 281 } 282 283 /** 284 * 285 */ 286 private function handleTable($table, $columns, $rows) { 287 $key = array(); 288 for ($c = 0; $c < $columns; $c++) { 289 $key[$c] = $this->database->getKey($table[0][$c]); 290 } 291 292 if (!in_array('', $key)) { 293 $this->handleDataSheet($table, $columns, $rows, $key); 294 } 295 else { 296 if ($columns == 2) { 297 $key = array(); 298 for ($r = 0; $r < $rows; $r++) { 299 $key[$r] = $this->database->getKey($table[$r][0]); 300 } 301 302 if (!in_array('', $key)) { 303 $this->handleDataCard($table, $rows, $key); 304 } 305 } 306 } 307 } 308 309 /** 310 * The data is organized in rows, one note per row. The first row contains the caption. 311 */ 312 private function handleDataSheet($table, $columns, $rows, $key) { 313 for ($r = 1; $r < $rows; $r++) { 314 $data = array(); 315 316 for ($c = 0; $c < $columns; $c++) { 317 $data[$key[$c]] = $table[$r][$c]; 318 } 319 320 $this->handleNote($data); 321 } 322 } 323 324 /** 325 * Every note is stored in a separate table. The first column of the table contains 326 * the caption, the second one contains the data. 327 */ 328 private function handleDataCard($table, $rows, $key) { 329 $data = array(); 330 331 for ($r = 0; $r < $rows; $r++) { 332 $data[$key[$r]] = $table[$r][1]; 333 } 334 335 $this->handleNote($data); 336 } 337 338 /** 339 * 340 */ 341 private function parseCode($call) { 342 switch ($call[1][1]) { 343 case 'bibtex': 344 $this->parseBibtex($call[1][0]); 345 break; 346 } 347 } 348 349 /** 350 * 351 */ 352 private function parseBibtex($text) { 353 foreach (refnotes_bibtex_parser::getInstance()->parse($text) as $data) { 354 $this->handleNote($data); 355 } 356 } 357 358 /** 359 * 360 */ 361 private function parseDataEntry($pluginData) { 362 if (preg_match('/\brefnotes\b/', $pluginData['classes'])) { 363 $data = array(); 364 365 foreach ($pluginData['data'] as $key => $value) { 366 if (is_array($value)) { 367 $data[$key . 's'] = implode(', ', $value); 368 } 369 else { 370 $data[$key] = $value; 371 } 372 } 373 374 $this->handleNote($data); 375 } 376 } 377 378 /** 379 * 380 */ 381 private function handleNote($data) { 382 $note = new refnotes_reference_database_note($this->id, $data); 383 384 list($namespace, $name) = $note->getNameParts(); 385 386 if ($name != '') { 387 if (!in_array($namespace, $this->namespace)) { 388 $this->namespace[] = $namespace; 389 } 390 391 $this->note[$namespace . $name] = $note; 392 } 393 } 394 395 /** 396 * 397 */ 398 public function getNamespaces() { 399 return $this->namespace; 400 } 401 402 /** 403 * 404 */ 405 public function getNotes() { 406 if (empty($this->note)) { 407 $this->parse(); 408 } 409 410 return $this->note; 411 } 412} 413 414//////////////////////////////////////////////////////////////////////////////////////////////////// 415class refnotes_reference_database_note extends refnotes_refnote { 416 417 private $nameParts; 418 419 /** 420 * Constructor 421 */ 422 public function __construct($source, $data) { 423 parent::__construct(); 424 425 $this->nameParts = array('', ''); 426 427 if ($source == '{configuration}') { 428 $this->initializeConfigNote($data); 429 } 430 else { 431 $this->initializePageNote($data); 432 } 433 434 $this->attributes['source'] = $source; 435 } 436 437 /** 438 * 439 */ 440 public function initializeConfigNote($data) { 441 $this->data['note-text'] = $data['text']; 442 443 unset($data['text']); 444 445 $this->attributes = $data; 446 } 447 448 /** 449 * 450 */ 451 public function initializePageNote($data) { 452 if (isset($data['note-name'])) { 453 if (preg_match('/^' . refnotes_note::getNamePattern('full-extended') . '$/', $data['note-name']) == 1) { 454 $this->nameParts = refnotes_namespace::parseName($data['note-name']); 455 } 456 457 unset($data['note-name']); 458 } 459 460 $this->data = $data; 461 } 462 463 /** 464 * 465 */ 466 public function getNameParts() { 467 return $this->nameParts; 468 } 469} 470 471//////////////////////////////////////////////////////////////////////////////////////////////////// 472class refnotes_reference_database_cache { 473 474 private $fileName; 475 private $cache; 476 private $requested; 477 private $updated; 478 479 /** 480 * Constructor 481 */ 482 public function __construct() { 483 global $conf; 484 485 $this->fileName = $conf['cachedir'] . '/refnotes.database.dat'; 486 487 $this->load(); 488 } 489 490 /** 491 * 492 */ 493 private function load() { 494 $this->cache = array(); 495 $this->requested = array(); 496 497 if (file_exists($this->fileName)) { 498 $this->cache = unserialize(io_readFile($this->fileName, false)); 499 } 500 501 foreach (array_keys($this->cache) as $fileName) { 502 $this->requested[$fileName] = false; 503 } 504 505 $this->updated = false; 506 } 507 508 /** 509 * 510 */ 511 public function isCached($fileName) { 512 $result = false; 513 514 if (array_key_exists($fileName, $this->cache)) { 515 if ($this->cache[$fileName]['time'] == @filemtime($fileName)) { 516 $result = true; 517 } 518 } 519 520 $this->requested[$fileName] = true; 521 522 return $result; 523 } 524 525 /** 526 * 527 */ 528 public function getNamespaces($fileName) { 529 return $this->cache[$fileName]['ns']; 530 } 531 532 /** 533 * 534 */ 535 public function update($fileName, $namespace) { 536 $this->cache[$fileName] = array('ns' => $namespace, 'time' => @filemtime($fileName)); 537 $this->updated = true; 538 } 539 540 /** 541 * 542 */ 543 public function save() { 544 $this->removeOldPages(); 545 546 if ($this->updated) { 547 io_saveFile($this->fileName, serialize($this->cache)); 548 } 549 } 550 551 /** 552 * 553 */ 554 private function removeOldPages() { 555 foreach ($this->requested as $fileName => $requested) { 556 if (!$requested && array_key_exists($fileName, $this->cache)) { 557 unset($this->cache[$fileName]); 558 559 $this->updated = true; 560 } 561 } 562 } 563} 564