1<?php 2 /** 3 * @license http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.html 4 * @author Francois Merciol <dokuplugin@merciol.fr> 5 * 6 * Plugin Glossary: manage forms for glossary 7 */ 8if(!defined('DOKU_INC')) 9 define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); 10if(!defined('DOKU_PLUGIN')) 11 define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 12 13/* Sort glossary by definition */ 14function cmpGlossary ($a, $b) { 15 if ($a['word'] == $b['word']) 16 return 0; 17 return ($a['word'] < $b['word']) ? -1 : 1; 18} 19 20class glossary { 21 22 // ============================================================ 23 // Config attributs 24 // ============================================================ 25 var $cacheRootDir; // root cache directory 26 var $cacheDir; // cache directory for specific namespace 27 var $dataRootDir; // root data directory 28 var $dataDir; // data directory for specific namespace for xml file 29 var $recentDays; // time during the clock is display to indicate a nex definition 30 var $maxIP; // max proposition per guest user 31 var $propGroup; // user group without limits of proposition 32 var $adminGroup; // admin group who validate proposition 33 var $transSep; // line separator for translate if not empty 34 35 // ============================================================ 36 // Constant attributs 37 // ============================================================ 38 var $configFile = "config.xml"; // file config name 39 var $prop = "prop-"; // prefix for proposal 40 var $def = "def-"; // prefix for definition // XXXX 41 var $poll = "poll-"; // prefix for polling (evaluation per definition) 42 var $form = "form-"; // subset used in form 43 var $hide = "hide-"; // subset not used in form 44 var $statusFields = // fields managed per step (status) 45 array ("prop-" => array ("date", "ip", "email", "ns", "ticket", "word", "translate", "why"), 46 "def-" => array ("date", "ip", "email", "ns", "ticket", "useTicket", "word", "translate"), 47 "poll-" => array ("word", "view", "up", "down"), 48 "form-" => array ("ticket", "useTicket", "word", "translate", "why"), 49 "hide-" => array ("date", "ip", "email", "ns")); 50 var $oddEven = // for row color 51 array ("odd", "even"); 52 var $imgDir; 53 54 // ============================================================ 55 // Transcient attributs 56 // ============================================================ 57 var $message = array (); // "notify" =>, "info" =>, "success" =>, "error" => 58 var $plugin; // link to wiki plugin information (conf, lang, ...) 59 var $NS; // namespace where definition could be found 60 var $lastNotification; // time of last proposal notification 61 var $lastNotificationReset; // time of last administrators acknowledgement 62 var $md5ns; // the ns directory 63 var $md5id; // the wiki id 64 var $sampleFile; // cache for sample 65 var $listFile; // cache for sample 66 67 // ============================================================ 68 function createDirIsNeeded ($dir) { 69 if (is_dir ($dir)) 70 return; 71 @mkdir ($dir); 72 @chmod ($dir, 0775); 73 } 74 function __construct ($plugin, $ns) { 75 $this->plugin = $plugin; 76 $this->imgDir = DOKU_REL.'lib/plugins/glossary/images/'; 77 global $conf; 78 $savedir = ((!$conf['savedir'] || strpos ($conf['savedir'], '.') === 0) ? DOKU_INC : "").$conf['savedir']."/"; 79 $this->cacheRootDir = $savedir."cache/glossary/"; 80 $this->dataRootDir = $savedir.trim ($this->plugin->getConf ('dataDir').'/'); 81 glossary::createDirIsNeeded ($this->cacheRootDir); 82 glossary::createDirIsNeeded ($this->dataRootDir); 83 $this->NS = cleanId (trim ($ns)); 84 $this->md5ns = md5 ($this->NS); 85 $this->cacheDir = $this->cacheRootDir.$this->md5ns."/"; 86 glossary::createDirIsNeeded ($this->cacheDir); 87 $this->dataDir = $this->dataRootDir.$this->md5ns."/"; 88 glossary::createDirIsNeeded ($this->dataDir); 89 $this->sampleFile = $this->cacheDir."sample.cache"; 90 $this->listFile = $this->cacheDir."list.cache"; 91 $this->recentDays = $this->getConf ('recentDays'); 92 $this->maxIP = $this->getConf ('maxIP'); 93 $this->adminGroup = trim ($this->getConf ('adminGroup')); 94 $this->propGroup = trim ($this->getConf ('propGroup')); 95 $this->transSep = trim ($this->getConf ('transSep')); 96 } 97 98 function getConf ($name) { 99 return $this->plugin->getConf ($name); 100 } 101 function getLang ($name) { 102 return $this->plugin->getLang ($name); 103 } 104 // function isAdmin ($name) { 105 // return $this->plugin->isAdmin ($name); 106 // } 107 function localFN ($name) { 108 return $this->plugin->localFN ($name); 109 } 110 111 /* messages container to be display before plugin */ 112 function message ($type, $text) { 113 if (isset ($this->message[$type])) 114 $this->message[$type] .= '<br/>'; 115 $this->message[$type] .= $text; 116 } 117 /* debug messages for admin only */ 118 function debug ($text) { 119 global $INFO; 120 if (in_array ('admin', $INFO ['userinfo'] ['grps'])) 121 $this->message ('notify', '<pre>'.$text.'</pre>'); 122 } 123 /* get next parity for row color */ 124 function nextOddEven (&$even) { 125 $result = $this->oddEven [$even]; 126 $even = 1 - $even; 127 return $result; 128 } 129 130 // ============================================================ 131 // Control rights 132 // ============================================================ 133 /* true if the user has the admin rights */ 134 function testAdminGroup () { 135 global $INFO; 136 return 137 isset ($INFO ['userinfo']['grps']) && 138 in_array ($this->adminGroup, $INFO ['userinfo']['grps']); 139 } 140 141 /* true if the user has no proposition limits */ 142 function testPropGroup () { 143 global $INFO; 144 return 145 isset ($INFO ['userinfo']['grps']) && 146 in_array ($this->propGroup, $INFO ['userinfo']['grps']); 147 } 148 149 /* true if limit occured (i.e. problems => can't continued)*/ 150 function maxIP (&$request) { 151 $ip = $request['ip']; 152 $count = 1; 153 $all = $this->readAllGlossary ($this->prop); 154 if (isset ($all[$this->md5id])) { 155 $this->message ('success', $this->getLang ('update')); 156 return false; 157 } 158 159 if ($this->testPropGroup ()) 160 return false; 161 162 foreach ($all as $record) 163 if ($record['ip'] == $ip) 164 $count++; 165 if ($count <= $this->maxIP) { 166 if ($count == $this->maxIP) 167 $this->message ('notify', $this->getLang ('lastIP')); 168 return false; 169 } 170 $this->message ('error', $this->getLang ('maxIP')); 171 return true; 172 } 173 174 // ============================================================ 175 // Control fields 176 // ============================================================ 177 function testNotEmpty () { 178 return 179 $this->wordOk () || $this->translateOk () || $this->translateOk () || $this->ticketOk (); 180 } 181 function wordOk () { 182 return isset ($_REQUEST ['glossary']['word']) && trim ($_REQUEST ['glossary']['word']) != ""; 183 } 184 function translateOk () { 185 return isset ($_REQUEST ['glossary']['translate']) && trim ($_REQUEST ['glossary']['translate']) != ""; 186 } 187 function ticketOk () { 188 return isset ($_REQUEST ['glossary']['ticket']) && trim ($_REQUEST ['glossary']['ticket']) != ""; 189 } 190 191 // ============================================================ 192 // Manage XML file 193 // ============================================================ 194 /* read lodging config */ 195 function readConfig ($dir) { 196 $fileName = $dir.$this->configFile; 197 if (!file_exists ($fileName)) 198 return false; 199 $xml = new DOMDocument ("1.0", "utf8"); 200 $xml->load ($fileName); 201 $root = $xml->documentElement; 202 $this->lastNotification = $root->getElementsByTagName ("lastNotification")->item (0)->nodeValue; 203 $this->lastNotificationReset = $root->getElementsByTagName ("lastNotificationReset")->item (0)->nodeValue; 204 return $root->getElementsByTagName ("nameSpace")->item (0)->nodeValue; 205 } 206 /* write lodging config */ 207 function writeConfig () { 208 if ($this->NS == false) 209 return; 210 $fileName = $this->dataDir.$this->configFile; 211 @mkdir ($this->dataDir); 212 @chmod ($this->dataDir, 0775); 213 $xml = new DOMDocument ("1.0", "utf8"); 214 $root = $xml->createElement ("glossary"); 215 $xml->appendChild ($root); 216 $root->appendChild ($xml->createElement ("nameSpace", htmlspecialchars ($this->NS))); 217 $root->appendChild ($xml->createElement ("lastNotification", htmlspecialchars ($this->lastNotification))); 218 $root->appendChild ($xml->createElement ("lastNotificationReset", htmlspecialchars ($this->lastNotificationReset))); 219 $xml->formatOutput = true; 220 $xml->save ($fileName); 221 chmod ($fileName, 0664); 222 } 223 224 /* read all propositions, definitions, polls */ 225 function readAllGlossary ($status) { 226 if (!is_dir ($this->dataDir)) 227 return; 228 if ($this->NS == false) 229 return; 230 $result = array (); 231 $exclude_array = explode ("|", ".|..|".$this->configFile); 232 $pathDirObj = opendir ($this->dataDir); 233 while (false !== ($file = readdir ($pathDirObj))) { 234 if (in_array (strtolower ($file), $exclude_array) || !preg_match ('#'.$status.'(.*)\.xml$#i', $file, $regs)) 235 continue; 236 $result[$regs[1]] = $this->readGlossary ($regs[1], $status, LOCK_SH); 237 } 238 uasort ($result, "cmpGlossary"); 239 return $result; 240 } 241 242 /* read one proposition, definition, poll */ 243 function readGlossary ($md5id, $status, $lock) { 244 $fileName = $this->dataDir.$status.$md5id.".xml"; 245 if (!file_exists ($fileName)) 246 return false; 247 $lock = LOCK_SH; 248 if ($lock != LOCK_SH) { 249 $fp = fopen ($fileName, "r+"); 250 if (!flock ($fp, $lock)) { 251 $this->message ("error", "can't lock file ".$fileName."."); 252 return; 253 } 254 } 255 $xml = new DOMDocument ("1.0", "utf8"); 256 $xml->load ($fileName); 257 $root = $xml->documentElement; 258 $result = array (); 259 foreach ($this->statusFields [$status] as $field) 260 $result [$field] = $root->getElementsByTagName ($field)->item (0)->nodeValue; 261 return $result; 262 } 263 /* write one proposition, definition, poll */ 264 function writeGlossary ($md5id, &$values, $status) { 265 if (! isset ($values['ns']) || $values['ns'] == "") 266 $values['ns'] = $this->NS; // XXX compatibilité 267 $xml = new DOMDocument ("1.0", "utf8"); 268 $root = $xml->createElement ("glossary"); 269 $xml->appendChild ($root); 270 foreach ($this->statusFields [$status] as $field) 271 $root->appendChild ($xml->createElement ($field, htmlspecialchars ($values [$field]))); 272 $xml->formatOutput = true; 273 $fileName = $this->dataDir.$status.$md5id.".xml"; 274 $xml->save ($fileName); 275 chmod ($fileName, 0664); 276 //flock (fopen ($fileName, "r+"), LOCK_UN); 277 } 278 /* remove one proposition, definition, poll */ 279 function removeGlossary ($md5id, $status) { 280 $fileName = $this->dataDir.$status.$md5id.".xml"; 281 @unlink ($fileName); 282 } 283 284 /* count file propositions, definitions, polls */ 285 function getGlosarySize ($status, $md5ns) { 286 $subDir = $this->dataRootDir.$md5ns."/"; 287 if (!is_dir ($subDir)) 288 return 0; 289 $result = 0; 290 $exclude_array = explode ("|", ".|..|".$this->configFile); 291 $pathDirObj = opendir ($subDir); 292 while (false !== ($file = readdir ($pathDirObj))) { 293 if (in_array (strtolower ($file), $exclude_array) || !eregi ($status.'(.*)\.xml$', $file, $regs)) 294 continue; 295 $result++; 296 } 297 return $result; 298 } 299 300 // ============================================================ 301 // Actions to performed 302 // ============================================================ 303 /* update proposition, definition */ 304 function updateRequest (&$request, $status) { 305 $reread = false; 306 $update = false; 307 $this->md5id = md5 (trim ($request['ticket'])); 308 $oldValues = $this->readGlossary ($this->md5id, $status, LOCK_SH); 309 if ($oldValues) { 310 foreach ($this->statusFields [$this->form] as $field) { 311 if ((!isset ($request[$field]) || $request[$field] == "") && 312 $request[$field] != $oldValues [$field]) { 313 $request[$field] = $oldValues [$field]; 314 $reread = true; 315 } elseif (isset ($request[$field]) && $request[$field] != "" && 316 $request[$field] != $oldValues [$field]) { 317 $update = true; 318 } 319 } 320 foreach ($this->statusFields [$this->hide] as $field) 321 $request[$field] = $oldValues [$field]; 322 if ($reread) 323 $this->message ('success', $this->getLang ('readData')); 324 return $update; 325 } 326 return true; 327 } 328 329 /* display poll */ 330 function printPoll ($arg) { 331 echo 332 '<div class="glossary toolTip">'.NL; 333 $this->printPollAjax (md5 (trim ($arg))); 334 echo '</div>'; 335 } 336 337 function printPollAjax ($md5id) { 338 $filename = $this->cacheDir."poll-".md5($md5id).".cache"; 339 if (file_exists ($filename)) { 340 echo file_get_contents ($filename); 341 return; 342 } 343 $poll = $this->readGlossary ($md5id, $this->poll, LOCK_SH); 344 list ($scoreVal, $scoreImg) = $this->getScore ($poll); 345 $text = 346 '<a onClick="glossaryPoll(this,\''.$md5id.'\',\'down\',\''.$this->NS.'\');">'.NL. 347 ' <img src="'.$this->imgDir.'face-sad.png" /><span>'.$this->getLang ('tipPollDown').'</span>'.NL. 348 '</a>'. 349 $scoreImg.NL. 350 '<a onClick="glossaryPoll(this,\''.$md5id.'\',\'up\',\''.$this->NS.'\');">'.NL. 351 ' <img src="'.$this->imgDir.'face-smile.png" /><span>'.$this->getLang ('tipPollUp').'</span>'.NL. 352 '</a>'; 353 if (!$poll|| ($poll['up']+$poll['down'] < 1)) 354 $text .= 355 '<br/><b>'.$this->getLang ('notPolledYet').'</b>'; 356 file_put_contents ($filename, $text); 357 echo $text; 358 } 359 360 function getScore ($poll) { 361 $scoreImg = '<img src="'.$this->imgDir.'score'; 362 if ($poll && $poll["up"] + $poll["down"] > 0) { 363 $scoreVal = (($poll["up"] - $poll["down"]) / 364 (max (5, $poll["up"] + $poll["down"]))); 365 $scoreImg .= '-'.intval (round (($scoreVal+1)*3)); 366 } else 367 $scoreVal = 0; 368 $scoreImg .= '.png"/>'; 369 return array ($scoreVal, $scoreImg); 370 } 371 372 /* update poll */ 373 function poll () { 374 $request = &$_REQUEST ['glossary']; 375 $md5id = $request['ticket']; 376 $opinion = $request['opinion']; 377 $all = $this->readAllGlossary ($this->def); 378 if (! isset ($all[$md5id]) || !in_array ($opinion, $this->statusFields [$this->poll])) { 379 $this->message ('error', $this->getLang ('noDef')); 380 return; 381 } 382 $poll = $this->readGlossary ($md5id, $this->poll, LOCK_EX); 383 if (!$poll) 384 $poll = array ("word" => $all[$md5id]['word'], "up" => 0, "down" => 0); 385 if ($poll [$opinion] != PHP_INT_MAX) 386 $poll [$opinion]++; 387 $this->writeGlossary ($md5id, $poll, $this->poll); 388 $this->message ('success', $this->getLang ('writePoll')); 389 @unlink ($this->cacheDir."poll-".md5($md5id).".cache"); 390 $this->printPollAjax ($md5id); 391 } 392 393 394 function incView ($md5id, $word) { 395 $poll = $this->readGlossary ($md5id, $this->poll, LOCK_EX); 396 if (!$poll) 397 $poll = array ("word" => $word, "view" => 0, "up" => 0, "down" => 0); 398 $poll ["view"]++; 399 $this->writeGlossary ($md5id, $poll, $this->poll); 400 return $poll; 401 } 402 403 function clearCache ($nsMd5) { 404 $exclude = ".|.."; 405 $exclude_array = explode("|", $exclude); 406 $allNsMd5 = array (); 407 if ($nsMd5) 408 $allNsMd5[] = $nsMd5; 409 else { 410 $pathDirObj = opendir ($cacheRootDir); 411 while (false !== ($file = readdir ($pathDirObj))) { 412 if (in_array (strtolower ($file), $exclude_array)) 413 continue; 414 $pathFile = $pathDir.$file; 415 if (!is_dir ($pathFile)) 416 continue; 417 $allNsMd5[] = $file; 418 } 419 } 420 foreach ($allNsMd5 as $nsMd5) { 421 $cacheDir = $this->cacheRootDir.$nsMd5."/"; 422 if (!is_dir ($cacheDir)) 423 break; 424 $pathDirObj = opendir ($cacheDir); 425 while (false !== ($file = readdir ($pathDirObj))) { 426 if (in_array (strtolower ($file), $exclude_array)) 427 continue; 428 $pathFile = $cacheDir.$file; 429 if (!is_file ($pathFile)) 430 continue; 431 if (eregi ('.*\.cache$', $file, $b)) 432 unlink ($pathFile); 433 } 434 @rmdir ($cacheDir); 435 } 436 } 437 438 // ============================================================ 439 function manageRecord (&$request, $status, $needCaptcha) { 440 if (!$this->testNotEmpty ()) 441 return; 442 443 if ($this->ticketOk ()) { 444 if (!$this->updateRequest ($request, $status)) 445 return; 446 } else { 447 $request ['ticket'] = substr (md5 (date ('YmdHis')), 0, 12); 448 $this->md5id = md5 (trim ($request ['ticket'])); 449 } 450 if ($needCaptcha && (!$captcha =& plugin_load ('helper', 'captcha') || !$captcha->check ())) { 451 $this->message ('error', $this->getLang ('badCaptcha')); 452 return; 453 } 454 if (!$this->wordOk ()) { 455 $this->message ('error', $this->getLang ('wordMandatory')); 456 return; 457 } 458 if (!$this->translateOk ()) { 459 $this->message ('error', $this->getLang ('translateMandatory')); 460 return; 461 462 } 463 if (! isset ($request['date']) || $request['date'] == "") { 464 $request['date'] = date ('YmdHis'); 465 $request['ip'] = $_SERVER ['REMOTE_ADDR']; 466 global $INFO; 467 if (isset ($INFO['userinfo']['mail'])) 468 $request['email'] = $INFO['userinfo']['mail']; 469 $request['ns'] = $this->NS; 470 } 471 unset ($request['operation']); 472 sleep (1); 473 if ($status == $this->prop && $this->maxIP ($request)) 474 return; 475 $this->writeGlossary ($this->md5id, $request, $status); 476 $this->message ('success', $this->getLang ('proposalRecorded').'<b>'.$request['ticket']."</b>."); 477 $this->adminNotification ($request, $status); 478 } 479 480 // ============================================================ 481 // Result for display 482 // ============================================================ 483 function getDefinitionSize () { 484 return $this->getGlosarySize ($this->def, $this->md5ns); 485 } 486 function getProposalSize () { 487 return $this->getGlosarySize ($this->prop, $this->md5ns); 488 } 489 function getPollSize () { 490 return $this->getGlosarySize ($this->poll, $this->md5ns); 491 } 492 function printProposal () { 493 global $INFO; 494 $needCaptcha = !(isset ($INFO ['userinfo']) && 495 isset ($INFO ['userinfo']['grps']) && 496 $INFO ['userinfo']['grps']); 497 $request = &$_REQUEST ['glossary']; 498 if ($request['operation'] == "record") 499 $this->manageRecord ($request, $this->prop, $needCaptcha); 500 echo 501 '<form method="post" action="" onsubmit="return glossaryAjax(this);">'.NL. 502 ' <table>'.NL. 503 ' <tr class="title">'.NL. 504 ' <th><img src="'.$this->imgDir.'stop.png" /> '.$this->getLang ('word').'</th>'.NL. 505 ' <th><img src="'.$this->imgDir.'one-way.png" /> '.$this->getLang ('translate').'</th>'.NL. 506 ' </tr><tr class="odd">'.NL. 507 ' <td>'.NL. 508 ' * <input type="text" name="glossary[word]" value="'.$request['word'].'" class="text" />'.NL. 509 ' </td><td>'.NL. 510 ' * <input type="text" name="glossary[translate]" value="'.$request['translate'].'" class="text" />'.NL. 511 ' </td>'.NL. 512 ' <tr class="title">'.NL. 513 ' <th>'.$this->getLang ('why').'</th>'.NL. 514 ' <th>'.NL. 515 ' <input type="hidden" name="glossary[operation]" value="record">'.NL. 516 ' <input type="hidden" name="glossary[ns]" value="'.$this->NS.'">'.NL. 517 ' <input type="submit" name="glossary[action]" value="'.$this->getLang (empty ($request['ticket']) ? 'proposal' : 'update').'" />'.NL. 518 ' <input type="reset" onClick="glossaryReset(this);glossaryUpdateProposalLabel(this.form)" value="'.$this->getLang ('new').'" />'.NL. 519 ' </th>'.NL. 520 ' </tr><tr class="odd">'.NL. 521 ' <td colspan="2"><textarea name="glossary[why]" class="why" />'.$request['why'].'</textarea></td>'.NL. 522 ' </tr><tr class="even">'.NL. 523 ' <td class="right">'.NL. 524 ' '.$this->getLang ('ticketIfUpdate').NL. 525 ' </td><td>'.NL. 526 ' <input type="text" name="glossary[ticket]" value="'.$request['ticket'].'" class="text" onChange="glossaryUpdateProposalLabel(this.form)" onKeypress="glossaryUpdateProposalLabel(this.form)" />'.NL. 527 ' </td>'.NL. 528 ' </tr>'.NL; 529 if ($needCaptcha && $captcha =& plugin_load ('helper', 'captcha')) 530 echo '<tr><td colspan=2>'.$captcha->getHTML ().'</td></tr>'.NL; 531 echo 532 ' </table>'.NL; 533 '</form>'; 534 } 535 536 // ============================================================ 537 function printSample () { 538 if (file_exists ($this->sampleFile) && 539 (time () - filemtime ($this->sampleFile) < $this->getConf ('sampleDelai'))) { 540 echo file_get_contents ($this->sampleFile); 541 return; 542 } 543 544 $all = $this->readAllGlossary ($this->def); 545 $keys = array_keys ($all); 546 $rand = array_rand ($keys); 547 $record = $all [$keys [$rand]]; 548 $imgDir = DOKU_REL.'lib/plugins/glossary/images/'; 549 foreach (explode ($this->transSep, $record['word']) as $word) 550 foreach (explode ($this->transSep, $record['translate']) as $translate) { 551 $word = trim ($word); 552 $translate = trim ($translate); 553 $pageId = trim ($record['useTicket']); 554 if (!$pageId) 555 $pageId = trim ($record['ticket']); 556 resolve_pageid ($this->NS, $pageId, $exists); 557 $cleanId = cleanId ($pageId); 558 $link = '<a class="wikilink1" href="'.DOKU_REL.$cleanId.'">'; 559 $text = 560 '<div>'.$this->getLang ('word').'<br/><img src="'.$imgDir.'stop.png" align="left"/> <span class="glossaryWord"> '.$link.$word.' </a></span></div>'.NL. 561 '<div>'.$this->getLang ('translate').'<br/><img src="'.$imgDir.'one-way.png" align="left"/> <span class="glossaryTranslate"> '.$link.$translate.' </a></span></div>'.NL; 562 file_put_contents ($this->sampleFile, $text); 563 echo $text; 564 return; 565 } 566 } 567 568 // ============================================================ 569 function clearListFile () { 570 @unlink ($this->listFile); 571 } 572 573 function printList () { 574 if ($this->testAdminGroup ()) { 575 echo '<div><form>'; 576 foreach (array ('clear', 'clearAll') as $action) 577 echo '<input value="'.$this->getLang ($action).'" onclick="javascript:glossarySendClear (\''.$action.'\', \''.$this->NS.'\')" type="button">'; 578 echo '</form></div>'; 579 } 580 if (file_exists ($this->listFile) && 581 (time () - filemtime ($this->listFile) < $this->getConf ('listDelai'))) { 582 echo file_get_contents ($this->listFile); 583 return; 584 } 585 586 $all = $this->readAllGlossary ($this->def); 587 $poll = $this->readAllGlossary ($this->poll); 588 $text = 589 '<table>'.NL. 590 ' <tr class="title">'.NL. 591 ' <th></th>'.NL. 592 ' <th class="toolTip">'.NL. 593 ' <a onClick="glossarySort(this,glossaryComparatorWord);"><span>'.$this->getLang ('tipWord').'</span><img src="'.$this->imgDir.'stop.png" /></a>'.NL. 594 ' <a onClick="glossarySort(this,glossaryComparatorTranslate);"><span>'.$this->getLang ('tipTranslate').'</span><img src="'.$this->imgDir.'one-way.png" /></a>'.NL. 595 ' <a onClick="glossarySort(this,glossaryComparatorDate);"><span>'.$this->getLang ('tipDate').'</span><img src="'.$this->imgDir.'clock.png" /></a>'.NL. 596 ' <a onClick="glossarySort(this,glossaryComparatorView);"><span>'.$this->getLang ('tipView').'</span><img src="'.$this->imgDir.'eye.png" /></a>'.NL. 597 ' <a onClick="glossarySort(this,glossaryComparatorScore);"><span>'.$this->getLang ('tipScore').'</span><img src="'.$this->imgDir.'score-all.png" width="32" /></a>'.NL. 598 ' </th>'.NL. 599 ' <th>'.$this->getLang ('why').'</th>'.NL. 600 ' <th><img src="'.$this->imgDir.'stop.png" /> '.$this->getLang ('word').'</th>'.NL. 601 ' <th colspan="2" class="toolTip" style="white-space: nowrap;">'.NL. 602 ' <form onsubmit="javascript:glossarySearch(this.elements[0]);return false;">'.NL. 603 ' <span>'.$this->getLang ('tipSearch').'</span><img src="'.$this->imgDir.'search.png" />'.NL. 604 ' <input type="text" name="glossary[search]" value="" class="search" keyup="glossarySearch(this);" onChange="glossarySearch(this);" />'.NL. 605 ' </form>'.NL. 606 ' </th>'.NL. 607 ' <th><img src="'.$this->imgDir.'one-way.png" /> '.$this->getLang ('translate').'</th>'.NL. 608 ' <th>'.$this->getLang ('poll').'</th>'.NL. 609 ' </tr>'; 610 $even = 0; 611 $recentTime = mktime (0, 0, 0, date ("n"), date ("j")-$this->recentDays, date ("Y")); 612 $recent = date ("YmdHis", $recentTime); 613 global $ID; 614 // XXX bug si empty ($this->transSep) 615 $canInc = true; 616 foreach ($all as $md5id => $record) 617 foreach (explode ($this->transSep, $record['word']) as $word) 618 foreach (explode ($this->transSep, $record['translate']) as $translate) { 619 $word = trim ($word); 620 $translate = trim ($translate); 621 $pageId = trim ($record['useTicket']); 622 if (!$pageId) 623 $pageId = trim ($record['ticket']); 624 resolve_pageid ($this->NS, $pageId, $exists); 625 $style = false; 626 $imgClock = ''; 627 if ($record['date'] > $recent) { 628 $opacity = 1; 629 if (preg_match ("#(?<Y>[0-9]{4})(?<m>[0-9]{1,2})(?<d>[0-9]{1,2})#", $record['date'], $dt_date)) { 630 $delta = (mktime (0, 0, 0, $dt_date["m"], $dt_date["d"], $dt_date["Y"]) - $recentTime) / 86400; 631 $opacity = ($delta)/($this->recentDays+1); 632 } 633 $imgClock = '<img src="'.$this->imgDir.'clock.png" style="opacity:'.$opacity.';" />'; 634 } 635 $cleanId = cleanId ($pageId); 636 if ($canInc && $ID == $cleanId) { 637 $poll [$md5id] = $this->incView ($md5id, $word); 638 $canInc = false; 639 } 640 $link = '<a href="'.DOKU_REL.$cleanId.'">'; 641 list ($scoreVal, $scoreImg) = $this->getScore ($poll [$md5id]); 642 $text .= 643 '<tr class="'.$this->nextOddEven ($even).'"'.NL. 644 ' word="'.$word.'" translate="'.$translate.'"'.NL. 645 ' date="'.$record['date'].'" view="'.$poll [$md5id]['view'].'" score="'.$scoreVal.'">'.NL. 646 ' <td class="count"></td>'.NL. 647 ' <td>'.$imgClock.'</td>'.NL. 648 ' <td class="why toolTip">'.$link.'<img src="'.$this->imgDir.'help.png"/><span>'.$this->getLang ('tipWhy').'</span></a></td>'.NL. 649 ' <td class="word" colspan="2">'.$link.$word.'</a></td>'.NL. 650 ' <td class="translate" colspan="2">'.$link.$translate.'</a></td>'.NL. 651 ' <td class="poll toolTip">'.$link.'<span>'.$this->getLang ('tipPoll').'</span>'.$scoreImg.'</a></td>'.NL. 652 ' </tr>'; 653 } 654 $text .= 655 NL. 656 '</table>'; 657 file_put_contents ($this->listFile, $text); 658 echo $text; 659 } 660 661 // ============================================================ 662 function manageUpdate (&$request) { 663 if (!$this->testAdminGroup ()) 664 return; 665 $this->manageRecord ($request, $this->def, false); 666 } 667 668 function manageRemove (&$request) { 669 if (!$this->testAdminGroup ()) 670 return; 671 foreach (array ($this->prop, $this->def) as $status) { 672 if (empty ($request[$status.'tickets'])) 673 continue; 674 foreach ($request[$status.'tickets'] as $ticket) { 675 $this->removeGlossary (md5 (trim ($ticket)), $status); 676 $this->message ('info', $ticket." ".$this->getLang ('ticketDeleted')); 677 if ($status == $this->def) { 678 $pageId = $ticket; 679 resolve_pageid ($this->NS, $pageId, $exists); 680 if ($exists) { 681 saveWikiText ($pageId, "", "wizard remove"); 682 $this->message ('info', $pageId." ".$this->getLang ('pageDeleted')); 683 } 684 } 685 } 686 } 687 } 688 689 // ============================================================ 690 function printManagedList ($status) { 691 $all = $this->readAllGlossary ($status); 692 echo 693 '<form method="post" action="" onsubmit="return glossaryAjax(this);">'.NL. 694 ' <table>'.NL. 695 ' <tr class="title">'.NL. 696 ' <th></th>'.NL; 697 foreach (array_diff ($this->statusFields [$status], array ("ns")) as $field) 698 echo 699 ' <th>'.$this->getLang ($field).'</th>'.NL; 700 echo 701 ' </tr>'; 702 $even = 0; 703 foreach ($all as $record) { 704 echo 705 '<tr class="'.$this->nextOddEven ($even).'">'.NL. 706 ' <td><input type="checkbox" name="glossary['.$status.'tickets][]" value="'.$record['ticket'].'"/></td>'.NL. 707 ' <td>'.substr ($record['date'],0, 8).'</td>'.NL. 708 ' <td>'.$record['ip'].'</td>'.NL. 709 ' <td>'.$record['email'].'</td>'.NL. 710 ' <td>'.$record['ticket'].'</td>'.NL; 711 if ($status == $this->def) 712 echo 713 ' <td>'.$record['useTicket'].'</td>'.NL; 714 echo 715 ' <td>'.$record['word'].'</td>'.NL. 716 ' <td>'.$record['translate'].'</td>'.NL; 717 if ($status == $this->prop) 718 echo 719 ' <td>'.str_replace ("\n", "<br/>\n", $record['why']).'</td>'.NL; 720 echo 721 ' </tr>'; 722 } 723 echo 724 '<tr>'.NL. 725 ' <td colspan="2">'.NL. 726 ' <input type="hidden" name="glossary[operation]" value="'.$status.'remove">'.NL. 727 ' <input type="hidden" name="glossary[ns]" value="'.$this->NS.'">'.NL. 728 ' <input type="submit" name="glossary[action]" value="'.$this->getLang ('remove').'"/></td>'.NL. 729 ' <td colspan="6"></td>'.NL. 730 ' </tr>'.NL. 731 ' </table>'.NL. 732 ' </form>'.NL; 733 } 734 735 // ============================================================ 736 function printManagedForm (&$request) { 737 echo 738 '<form method="post" action="" onsubmit="return glossaryAjax(this);">'.NL. 739 ' <table>'.NL. 740 ' <tr class="title">'.NL. 741 ' <th></th>'.NL. 742 ' <th><img src="'.$this->imgDir.'stop.png" /> '.$this->getLang ('word').'</th>'.NL. 743 ' <th><img src="'.$this->imgDir.'one-way.png" /> '.$this->getLang ('translate').'</th>'.NL. 744 ' </tr><tr class="'.$this->oddEven [0].'">'.NL. 745 ' <td><input type="reset" onClick="glossaryReset(this)" value="'.$this->getLang ('new').'" /></td>'.NL. 746 ' <td><input type="text" name="glossary[word]" value="'.$request['word'].'" class="text" /></td>'.NL. 747 ' <td><input type="text" name="glossary[translate]" value="'.$request['translate'].'" class="text" /></td>'.NL. 748 ' </tr><tr class="title">'.NL. 749 ' <th></th>'.NL. 750 ' <th>'.$this->getLang ('ticket').'</th>'.NL. 751 ' <th>'.$this->getLang ('useTicket').'</th>'.NL. 752 ' </tr><tr class="'.$this->oddEven [0].'">'.NL. 753 ' <td>'.NL. 754 ' <input type="hidden" name="glossary[operation]" value="'.$this->def.'update">'.NL. 755 ' <input type="hidden" name="glossary[ns]" value="'.$this->NS.'">'.NL. 756 ' <input type="submit" name="glossary[action]" value="'.$this->getLang ('update').'"/></td>'.NL. 757 ' <td><input type="text" name="glossary[ticket]" value="'.$request['ticket'].'" class="text" /></td>'.NL. 758 ' <td><input type="text" name="glossary[useTicket]" value="'.$request['useTicket'].'" class="text" /></td>'.NL. 759 ' </tr>'.NL. 760 ' </table>'.NL. 761 '</form>'.NL; 762 } 763 764 // ============================================================ 765 function createPage (&$request) { 766 if (!$this->ticketOk ()) 767 return; 768 $pageId = trim ($request['useTicket']); 769 if (!$pageId) 770 $pageId = trim ($request['ticket']); 771 $this->md5id = md5 ($pageId); 772 $values = $this->readGlossary ($this->md5id, $this->def, LOCK_SH); 773 if (!$values) 774 return; 775 resolve_pageid ($this->NS, $pageId, $exists); 776 if ($exists) 777 return; 778 $pageTpl = io_readfile ($this->localFN ('pageTemplate')); 779 $wordTpl = io_readfile ($this->localFN ('wordTemplate')); 780 $translateTpl = io_readfile ($this->localFN ('translateTemplate')); 781 $wordList = ''; 782 foreach (explode ($this->transSep, $values['word']) as $word) 783 $wordList .= str_replace ("@@WORD@@", $word, $wordTpl); 784 $translateList = ''; 785 foreach (explode ($this->transSep, $values['word']) as $word) 786 $translateList .= str_replace ("@@TRANSLATE@@", $word, $translateTpl); 787 $replace = array ('@@WORD@@' => $wordList, 788 '@@TRANSLATE@@' => $translateList, 789 '@@POL@@' => $values['ticket'], 790 '@@PROPOSITIONPAGE@@' => $this->getConf ('propositionPage')); 791 $content = str_replace (array_keys ($replace), array_values ($replace), $pageTpl); 792 saveWikiText ($pageId, $content, "wizard creation"); 793 $this->message ('success', $this->getLang ('createPage')); 794 } 795 796 // ============================================================ 797 function glossariesRemove (&$request) { 798 if (!$this->testAdminGroup ()) 799 return; 800 if (empty ($request['dir'])) 801 return; 802 foreach ($request['dir'] as $md5ns) { 803 $subDir = $this->dataRootDir.$md5ns.'/'; 804 $ns = $this->readConfig ($subDir); 805 if ($this->getGlosarySize ($this->def, $md5ns)+$this->getGlosarySize ($this->prop, $md5ns) > 0) 806 $this->message ('error', $this->getLang ('glossaryNotEmpty').$ns." (".$md5ns.")."); 807 else { 808 if (!is_dir ($subDir)) 809 continue; 810 $pathDirObj = opendir ($subDir); 811 while (false !== ($file = readdir ($pathDirObj))) { 812 if (!eregi ($this->poll.'(.*)\.xml$', $file, $regs)) 813 continue; 814 @unlink ($subDir.$file); 815 } 816 @unlink ($subDir.$this->configFile); 817 @rmdir ($subDir); 818 $this->message ('success', $this->getLang ('glossaryRemoved').$ns." (".$md5ns.")."); 819 } 820 } 821 } 822 823 function printGlossariesList () { 824 if (!is_dir ($this->dataRootDir)) 825 return; 826 $list = array (); 827 $exclude_array = explode ("|", ".|.."); 828 $pathDirObj = opendir ($this->dataRootDir); 829 while (false !== ($file = readdir ($pathDirObj))) { 830 $subDir = $this->dataRootDir.$file.'/'; 831 if (in_array (strtolower ($file), $exclude_array) || !is_dir ($subDir)) 832 continue; 833 $ns = $this->readConfig ($subDir); 834 $list [$file] = 835 array ($ns, 836 $this->getGlosarySize ($this->def, $file), 837 $this->getGlosarySize ($this->prop, $file), 838 $this->getGlosarySize ($this->poll, $file)); 839 } 840 $even = 0; 841 echo 842 '<form method="post" action="" onsubmit="return glossaryAjax(this);">'.NL. 843 ' <table>'.NL. 844 ' <tr class="title">'.NL. 845 ' <th></th><th>directory</th><th>nameSpace</th><th>definition</th><th>proposal</th><th>poll</th>'.NL. 846 ' </tr>'; 847 foreach ($list as $md5ns => $data) { 848 list ($ns, $def, $prop, $poll) = $data; 849 echo 850 '<tr class="'.$this->nextOddEven ($even).'">'.NL. 851 ' <td><input type="checkbox" name="glossary[dir][]" value="'.$md5ns.'"/></td> '.NL. 852 ' <td>'.$md5ns.'</td>'.NL. 853 ' <td>'.$ns.'</td>'.NL. 854 ' <td>'.$def.'</td>'.NL. 855 ' <td>'.$prop.'</td>'.NL. 856 ' <td>'.$poll.'</td>'.NL. 857 ' </tr>'; 858 } 859 echo 860 '<tr class="'.$this->nextOddEven ($even).'">'.NL. 861 ' <td colspan="2">'.NL. 862 ' <input type="hidden" name="glossary[operation]" value="glos-remove">'.NL. 863 ' <input type="submit" name="glossary[action]" value="'.$this->getLang ('remove').'"/></td>'.NL. 864 ' <td colspan="4"></td>'.NL. 865 ' </tr>'.NL. 866 ' </table>'.NL. 867 '</form>'; 868 } 869 870 // ============================================================ 871 function adminProposal () { 872 if (!$this->testAdminGroup ()) 873 return; 874 $request = &$_REQUEST ['glossary']; 875 if ($request['operation'] == $this->prop."remove") 876 $this->manageRemove ($request); 877 $this->printManagedList ($this->prop); 878 $this->resetAdminNotification (); 879 } 880 881 function adminDefinition () { 882 if (!$this->testAdminGroup ()) 883 return; 884 $request = &$_REQUEST ['glossary']; 885 if ($request['operation'] == $this->def."remove") 886 $this->manageRemove ($request); 887 if ($request['operation'] == $this->def."update") { 888 $this->manageUpdate ($request); 889 $this->createPage ($request); 890 } 891 $this->clearListFile (); 892 $this->printManagedForm ($request); 893 $this->printManagedList ($this->def); 894 } 895 896 function adminGlossaries () { 897 if (!$this->testAdminGroup ()) 898 return; 899 $request = &$_REQUEST ['glossary']; 900 if ($request['operation'] == "glos-remove") 901 $this->glossariesRemove ($request); 902 $this->printGlossariesList (); 903 } 904 905 // ============================================================ 906 function resetAdminNotification () { 907 $subDir = $this->dataRootDir.$this->md5ns.'/'; 908 $this->readConfig ($subDir); 909 $this->lastNotificationReset = date ('YmdHis'); 910 $this->writeConfig (); 911 } 912 913 function adminNotification ($request, $status) { 914 $this->readConfig ($this->dataRootDir.$this->md5ns.'/'); 915 if ($this->lastNotification <= $this->lastNotificationReset) { 916 $this->lastNotification = date ('YmdHis'); 917 918 global $auth; 919 $users = $auth->retrieveUsers (); 920 foreach ($users as $user => $userinfo) { 921 $mailSubject = $this->getLang ('notifySubject'); 922 $mailContent = $this->getLang ('notifyContent'); 923 $mailContent = str_replace ('@WORD@', $request['word'], $mailContent); 924 $mailContent = str_replace ('@TRANSLATE@', $request['translate'], $mailContent); 925 926 if (in_array ($this->adminGroup, $userinfo ['grps'])) { 927 $mailTo = $userinfo['mail']; 928 mail_send ($mailTo, $mailSubject, $mailContent); 929 // XXX $this->debug (print_r (array ($mailTo, $mailSubject, $mailContent), 1)); 930 } 931 } 932 933 $this->writeConfig (); 934 } 935 } 936 937 // ============================================================ 938} 939