1<?php 2 3if(!defined('DOKU_PLUGIN')) die('meh'); 4require_once(DOKU_PLUGIN.'siteexport/inc/settings.php'); 5require_once(DOKU_PLUGIN.'siteexport/inc/debug.php'); 6 7class siteexport_functions extends DokuWiki_Plugin 8{ 9 public $debug = null; 10 public $settings = null; 11 12 public function siteexport_functions($init=true) 13 { 14 if ( $init ) 15 { 16 $this->debug = new siteexport_debug(); 17 $this->settings = new settings_plugin_siteexport_settings($this); 18 19 $this->debug->setDebugLevel($this->settings->getConf('debugLevel')); 20 $this->debug->setDebugFile ($this->settings->getConf('debugFile')); 21 22 $this->debug->message("Settings completed: zipFile", $this->settings->zipFile, 1); 23 } 24 } 25 26 public function getPluginName() 27 { 28 return 'siteexport'; 29 } 30 31 public function downloadURL() 32 { 33 return ml($this->settings->origZipFile, array('cache' => 'nocache', 'siteexport' => $this->settings->pattern), true, '&'); 34 } 35 36 public function checkIfCacheFileExistsForFileWithPattern($file, $pattern) 37 { 38 if ( !@file_exists($file) ) 39 { 40 // If the cache File does not exist, move the newly created one over ... 41 $newCacheFile = mediaFN($this->getSpecialExportFileName($this->settings->origZipFile, $pattern)); 42 43 if ( !@file_exists($newCacheFile) ) 44 { 45 $this->debug->message("The export must have gone wrong. The cached file does not exist.", array("pattern" => $pattern, "original File" => $this->settings->origZipFile, "expected cached file" => $newCacheFile), 3); 46 } 47 48 $status = io_rename($newCacheFile, $file); 49 $this->debug->message("had to move another original file over. Did it work? " . ($status ? 'Yes, it did.' : 'No, it did not.'), null, 2 ); 50 } 51 } 52 53 54 /** 55 * Returns an utf8 encoded Namespace for a Page and input Namespace 56 * @param $NS 57 * @param $PAGE 58 */ 59 function getNamespaceFromID($NS, &$PAGE) { 60 global $conf; 61 // Check current page - if its an NS add the startpage 62 $clean = true; 63 resolve_pageid(getNS($NS), $NS, $clean); 64 if ( ! page_exists($NS) && array_pop(explode(':', $NS)) != strtolower($conf['start'] )) { // Compare to lowercase since clean lowers it. 65 $NS .= ':' . $conf['start']; 66 resolve_pageid(getNS($NS), $NS, $clean); 67 } 68 69 $PAGE = noNS($NS); 70 $NS = getNS($NS); 71 72 return utf8_encodeFN(str_replace(':', '/', $NS)); 73 } 74 75 /** 76 * create a file name for the page 77 **/ 78 public function getSiteName($ID, $overrideRewrite=false) { 79 global $conf; 80 81 if ( empty($ID) ) return false; 82 83 $url = $this->wl($this->cleanID($ID), null, true, null, null, $overrideRewrite); // this must be done with rewriting set to override 84 //$url = $this->wl($this->cleanID($ID), null, true); // this must be done with rewriting set to override 85 $uri = @parse_url($url); 86 if ( $uri['path'][0] == '/' ) { 87 $uri['path'] = substr($uri['path'], 1); 88 } 89 90 return $this->shortenName($uri['path'] . '.' . $this->settings->fileType); 91 } 92 93 /** 94 * get the Title for the page 95 **/ 96 public function getSiteTitle($ID) { 97 if (useHeading('content') && $ID) { 98 $heading = p_get_first_heading($ID,true); 99 if ($heading) { 100 return $this->xmlEntities($heading); 101 } 102 } 103 return ucwords($this->xmlEntities(array_pop(explode(':', $ID)))); 104 } 105 106 /** 107 * Encoding ()taken from DW - but without needing the renderer 108 **/ 109 public function xmlEntities($string) { 110 return htmlspecialchars($string,ENT_QUOTES,'UTF-8'); 111 } 112 113 /** 114 * Create name for the file inside the zip and the replacements 115 **/ 116 function shortenName($NAME) 117 { 118 $NS = $this->settings->exportNamespace; 119 $NAME = preg_replace("%^" . DOKU_BASE . "%", "", $NAME); 120 $NAME = preg_replace("%^(_media/)?(" . $NS . "/)?%", "", $NAME); 121 122 $this->debug->message("Shortening file to '$NAME'", null, 1); 123 return $NAME; 124 } 125 126 /** 127 * Remove unwanted chars from ID 128 * 129 * Cleans a given ID to only use allowed characters. Accented characters are 130 * converted to unaccented ones 131 * 132 * @author Andreas Gohr <andi@splitbrain.org> 133 * @param string $raw_id The pageid to clean 134 * @param boolean $ascii Force ASCII 135 * @param boolean $media Allow leading or trailing _ for media files 136 */ 137 function cleanID($raw_id,$ascii=false,$media=false){ 138 global $conf; 139 global $lang; 140 static $sepcharpat = null; 141 142 global $cache_cleanid; 143 $cache = & $cache_cleanid; 144 145 // check if it's already in the memory cache 146 if (isset($cache[(string)$raw_id])) { 147 return $cache[(string)$raw_id]; 148 } 149 150 $sepchar = $conf['sepchar']; 151 if($sepcharpat == null) // build string only once to save clock cycles 152 $sepcharpat = '#\\'.$sepchar.'+#'; 153 154 $id = trim((string)$raw_id); 155 // $id = utf8_strtolower($id); // NO LowerCase for us! 156 157 //alternative namespace seperator 158 $id = strtr($id,';',':'); 159 if($conf['useslash']){ 160 $id = strtr($id,'/',':'); 161 }else{ 162 $id = strtr($id,'/',$sepchar); 163 } 164 165 if($conf['deaccent'] == 2 || $ascii) $id = utf8_romanize($id); 166 if($conf['deaccent'] || $ascii) $id = utf8_deaccent($id,-1); 167 168 //remove specials 169 $id = utf8_stripspecials($id,$sepchar,'\*'); 170 171 if($ascii) $id = utf8_strip($id); 172 173 //clean up 174 $id = preg_replace($sepcharpat,$sepchar,$id); 175 $id = preg_replace('#:+#',':',$id); 176 $id = ($media ? trim($id,':.-') : trim($id,':._-')); 177 $id = preg_replace('#:[:\._\-]+#',':',$id); 178 179 $cache[(string)$raw_id] = $id; 180 return($id); 181 } 182 183 184 /** 185 * This builds a link to a wikipage - changed for internal use here 186 * 187 * It handles URL rewriting and adds additional parameter if 188 * given in $more 189 * 190 * @author Andreas Gohr <andi@splitbrain.org> 191 */ 192 193 function wl($id='',$more='',$abs=false,$sep='&', $IDexists=true, $overrideRewrite=false, $hadBase=false){ 194 global $conf; 195 196 $this->debug->message("Starting to build WL-URL for '$id'", null, 1); 197 198 if(is_array($more)){ 199 $more = buildURLparams($more,$sep); 200 }else{ 201 $more = str_replace(',',$sep,$more); 202 } 203 204 $id = idfilter($id); 205 206 if($abs){ 207 $xlink = DOKU_URL; 208 if ( !$IDexists && !$hadBase ) { // If the file does not exist, we have to remove the base. This link my be one to an parallel BASE. 209 $xlink = preg_replace('#' . DOKU_BASE . '$#', '', $xlink); 210 } 211 }else if ($IDexists || $hadBase) { // if the ID does exist, we may add the base. 212 $xlink = DOKU_BASE; 213 } else{ 214 $xlink = ""; 215 } 216 217 // $this->debug->message("internal WL function Before Replacing: '$xlink'", array(DOKU_REL, DOKU_URL, DOKU_BASE, $xlink), 2); 218 $xlink = preg_replace('#(?<!http:|https:)//+#','/', ($abs ? '' : '/') . "$xlink/"); // ensure slashes at beginning and ending, but strip doubles 219 $this->debug->message("'$xlink'", array(DOKU_REL, DOKU_URL, DOKU_BASE, $xlink), 2); 220 221 if ( $overrideRewrite ) { 222 $this->debug->message("Override enabled.", null, 1); 223 $id = strtr($id,':','/'); 224 225 $xlink .= $id; 226 if($more) $xlink .= '?'.$more; 227 } else { 228 if($conf['userewrite'] == 2 ){ 229 $xlink .= DOKU_SCRIPT.'/'.$id; 230 if($more) $xlink .= '?'.$more; 231 }elseif($conf['userewrite'] ){ 232 $xlink .= $id; 233 if($more) $xlink .= '?'.$more; 234 }elseif($id){ 235 $xlink .= DOKU_SCRIPT.'?id='.$id; 236 if($more) $xlink .= $sep.$more; 237 }else{ 238 $xlink .= DOKU_SCRIPT; 239 if($more) $xlink .= '?'.$more; 240 } 241 } 242 243 $this->debug->message("internal WL function result: '$xlink'", null, 2); 244 245 return $xlink; 246 } 247 248 /** 249 * Create the export file name - this is the file where everything is being stored 250 * @param $FILE 251 * @param $PATTERN - additional pattern for re-using old files 252 */ 253 public function getSpecialExportFileName($FILE, $PATTERN=null) { 254 255 if ( empty($FILE) ) 256 { 257 $FILE = $this->settings->origZipFile; 258 } 259 260 if ( empty($PATTERN) && empty($this->settings->pattern) ){ 261 $this->debug("Generating an internal md5 pattern. This will go wrong - and won't cache properly."); 262 $PATTERN = md5(microtime(false)); 263 } 264 265 // Set Pattern Global for other stuff 266 if ( empty($this->settings->pattern) ) { 267 $this->settings['pattern'] = $PATTERN; 268 } else { 269 $PATTERN = $this->settings->pattern; 270 } 271 272 $FA = explode('.', $FILE); 273 $EXT = array_pop($FA); 274 array_push($FA, 'auto'); 275 array_push($FA, $PATTERN); 276 array_push($FA, $EXT); 277 278 $fileName = implode('.', $FA); 279 $this->debug->message("Export Filename for '$FILE' will be: '$fileName'", null, 2); 280 return $fileName; 281 } 282 283 public function getCacheFileNameForPattern($PATTERN = null) 284 { 285 if ( $PATTERN == null ) { 286 $PATTERN = $this->settings->pattern; 287 } 288 289 return getCacheName($this->getSpecialExportFileName($this->settings->origZipFile, $PATTERN), '.' . basename(mediaFN($this->settings->origZipFile)) ); 290 } 291 292 function startRedirctProcess($counter) { 293 global $ID; 294 295 $URL = wl($ID); 296 297 $additionalParameters = $_REQUEST; 298 $additionalParameters['startcounter'] = $counter; 299 $additionalParameters['pattern'] = $this->settings->pattern; 300 301 unset($additionalParameters['id']); 302 unset($additionalParameters['u']); 303 unset($additionalParameters['p']); 304 unset($additionalParameters['r']); 305 unset($additionalParameters['http_credentials']); 306 307 $this->addAdditionalParametersToURL($URL, $additionalParameters); 308 $this->debug->message("Redirecting to '$URL'", null, 2); 309 310 send_redirect($URL); 311 exit(0); // Should not be reached, but anyways 312 } 313 314 /** 315 * Builds additional Parameters into the URL given 316 * @param $URL 317 * @param $newAdditionalParameters 318 */ 319 function addAdditionalParametersToURL(&$URL, $newAdditionalParameters) { 320 321 // Add additionalParameters 322 if ( !empty($newAdditionalParameters) ) { 323 foreach($newAdditionalParameters as $key => $value ) { 324 if ( empty($key) || empty($value) ) { continue; } 325 326 $append = ''; 327 328 if ( is_array($value) ) { 329 foreach( array_values($value) as $aValue ) { // Array Handling 330 $URL .= (strstr($URL, '?') ? '&' : '?') . $key . "[]=$aValue"; 331 } 332 } else { 333 $append = "$key=$value"; 334 $URL .= empty($append) || strstr($URL, $append) ? '' : (strstr($URL, '?') ? '&' : '?') . $append; 335 } 336 337 } 338 } 339 } 340 341 /** 342 * Cleans the wiki variables and returns a rebuild URL that has the new variables at hand 343 * @param $data 344 */ 345 function prepare_POSTData($data) 346 { 347 $NS = !empty($data['ns']) ? $data['ns'] : $data['id']; 348 349 $this->removeWikiVariables($data); 350 $data['do'] = 'siteexport'; 351 $additionalKeys = ''; 352 353 ksort($data); 354 355 $this->debug->message("Prepared POST data:", $data, 1); 356 357 foreach( $data as $key => $value ) { 358 359 if ( !is_array($value) ) { continue; } 360 $this->debug->message("Found inner Array:", $value, 1); 361 362 asort($value); 363 foreach ( $value as $innerKey => $aValue ) 364 { 365 if ( is_numeric($innerKey)) 366 { 367 $innerKey = ''; 368 } 369 370 $additionalKeys .= "&$key" . "[$innerKey]=$aValue"; 371 } 372 373 unset($data[$key]); 374 } 375 376 return wl($NS, $data, true, '&') . $additionalKeys; 377 } 378 379 /** 380 * Parses a String into a $_REQUEST Like variant. You have to tell if a decode of the values is needed 381 * @param $inputArray 382 * @param $decode 383 */ 384 public function parseStringToRequestArray($inputArray, $decode=false) 385 { 386 global $plugin_controller; 387 388 $outputArray = $inputArray; 389 if ( !is_array($inputArray) ) 390 { 391 $intermediate = str_replace("&", "&", $inputArray); 392 393 $outputArray = array(); 394 foreach( explode("&", $intermediate) as $param ) { 395 list($key, $value) = explode("=", $param, 2); 396 397 // This is needed if we do want to calculate $_REQUEST for a non HTTP-Request 398 if ( $decode) 399 { 400 $value = urldecode($value); 401 } 402 403 if ( empty($key) ) { continue; } // Don't check on Value, because there may be only the key that should be preserved 404 405 if ( substr($key, -2) == '[]' ) { 406 $key = substr($key, 0, -2); 407 if ( !is_array($outputArray[$key]) ) { 408 $outputArray[$key] = array(); 409 } 410 411 array_push($outputArray[$key], $value); // Array Handling 412 } else { 413 $outputArray[$key] = $value; 414 } 415 } 416 } 417 418 if ( !empty($outputArray['diPlu']) ) { 419 420 $allPlugins = array(); 421 foreach($plugin_controller->getList(null,true) as $plugin ) { 422 // check for CSS or JS 423 if ( !file_exists(DOKU_PLUGIN."$plugin/script.js") && !file_exists(DOKU_PLUGIN."$p/style.css") ) { continue; } 424 $allPlugins[] = $plugin; 425 } 426 427 if ( count($outputArray['diPlu']) > (count($allPlugins) / 2) ) { 428 $outputArray['diInv'] = 1; 429 $outputArray['diPlu'] = array_diff($allPlugins, $outputArray['diPlu']); 430 } 431 } 432 433 return $outputArray; 434 } 435 436 /** 437 * Remove certain fields from the list. 438 * @param $removeArray 439 * @param $advanced 440 * @param $isString 441 */ 442 function removeWikiVariables(&$removeArray, $advanced=false, $isString=false) { 443 444 $removeArray = $this->parseStringToRequestArray($removeArray); 445 446 // 2010-08-23 - If there is still the media set, retain the id for e.g. detail.php 447 if ( !isset($removeArray['media']) ) { 448 unset($removeArray['id']); 449 } 450 451 unset($removeArray['do']); 452 unset($removeArray['ns']); 453 unset($removeArray['call']); 454 unset($removeArray['sectok']); 455 unset($removeArray['rndval']); 456 unset($removeArray['tseed']); 457 unset($removeArray['http_credentials']); 458 unset($removeArray['u']); 459 unset($removeArray['p']); 460 unset($removeArray['r']); 461 unset($removeArray['base']); 462 unset($removeArray['siteexport']); 463 unset($removeArray['DokuWiki']); 464 unset($removeArray['cronOverwriteExisting']); 465 466 if ( $removeArray['renderer'] == 'xhtml' ) { 467 $removeArray['do'] = 'export_' . $removeArray['renderer']; 468 unset($removeArray['renderer']); 469 } 470 471 if ( $advanced ) { 472 if ( $removeArray['renderer'] != 'xhtml' && !empty($removeArray['renderer']) ) { 473 $removeArray['do'] = 'export_' . $removeArray['renderer']; 474 } 475 476 // 2010-08-25 - Need fakeMedia for some _detail cases with rewrite = 2 477 if ( isset($removeArray['fakeMedia']) ) { 478 unset($removeArray['media']); 479 unset($removeArray['fakeMedia']); 480 } 481 482 /* remove internal params */ 483 unset($removeArray['ens']); 484 unset($removeArray['renderer']); 485 unset($removeArray['site']); 486 unset($removeArray['namespace']); 487 unset($removeArray['exportbody']); 488 unset($removeArray['addParams']); 489 unset($removeArray['template']); 490 unset($removeArray['eclipseDocZip']); 491 unset($removeArray['useTocFile']); 492 unset($removeArray['JavaHelpDocZip']); 493 unset($removeArray['depth']); 494 unset($removeArray['depthType']); 495 unset($removeArray['startcounter']); 496 unset($removeArray['pattern']); 497 unset($removeArray['TOCMapWithoutTranslation']); 498 499 // Keep custom options 500 if ( is_array($removeArray['customOptions']) ) 501 { 502 foreach( $removeArray['customOptions'] as $key => $value ) 503 { 504 $removeArray[$key] = $value; 505 } 506 } 507 unset($removeArray['customOptions']); 508 } 509 510 if ( $isString && is_array($removeArray) ) { 511 $intermediate = $removeArray; 512 $removeArray = array(); 513 514 foreach ( $intermediate as $key => $value ) { 515 if ( is_array($value) ) { 516 foreach( array_values($value) as $aValue ) { // Array Handling 517 $removeArray[] = $key . "[]=$aValue"; 518 } 519 } else { 520 $value = trim($value); 521 522 $removeArray[] = "$key" . ( ((empty($value) && intval($value) !== 0)) || $value == '' ? '' : "=$value" ); // If the Value is empty, the Key must be preserved 523 } 524 } 525 526 //$removeArray = implode( ($this->settings->fileType == 'pdf' ? "&" : "&"), $removeArray); 527 $removeArray = implode( "&", $removeArray); // The & made problems with the HTTPClient / Apache. It should not be a problem to have & 528 } 529 } 530 531 /** 532 * authenticate for direct downloads 533 **/ 534 function basic_authentication() { 535 if (!isset($_SERVER['PHP_AUTH_USER'])) { 536 $this->debug->message("Needs Authentication.", null, 2); 537 header('WWW-Authenticate: Basic realm="Siteexport Authentication"'); 538 header('HTTP/1.0 401 Unauthorized'); 539 print 'Unauthorized'; // print has to stay here 540 exit; 541 } 542 543 return array($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']); 544 } 545 546 /** 547 * returns a hashed name for the parameters 548 * @param $parameters 549 */ 550 public function cronJobNameForParameters($parameters) 551 { 552 return md5($parameters); 553 } 554 555 /** 556 * Takes an URL and transforms it into the path+query part 557 * Used several times, e.g. for genering the hash for the cache file 558 * @param $url 559 */ 560 public function urlToPathAndParams($url) 561 { 562 $query = parse_url($url, PHP_URL_QUERY); 563 $path = preg_replace(":^".DOKU_REL.":", "", parse_url($url, PHP_URL_PATH)); 564 return "{$path}?{$query}"; 565 } 566 567 /** 568 * Transforms an $_REQUEST into a Hash that can be used for cron and cache file 569 * @param $request 570 */ 571 public function requestParametersToCacheHash($request) 572 { 573 $params = $this->urlToPathAndParams($this->prepare_POSTData($request)); 574 $this->debug->message("Calculated the following Cache Hash URL: ", $params, 2); 575 return $this->cronJobNameForParameters($params); 576 } 577 578 /** 579 * Check a replaceID against a baseID - and make the replaceID relative against it 580 * @param $replaceID - ID which will be made relative if needed 581 * @param $baseID - ID which is the reference to be made relative against 582 */ 583 public function getRelativeURL($replaceID, $baseID) 584 { 585 $origReplaceID = $replaceID; 586 587 $replaceTmp = cleanID($replaceID); 588 $file = noNS($replaceTmp); 589 590 $replaceID = getNS($replaceTmp); 591 $baseID = getNS($baseID); 592 593 $replaceParts = explode(':', $replaceID); 594 $baseParts = explode(':', $baseID); 595 $exportNSParts = explode(':', cleanID($this->settings->exportNamespace)); 596 597 $newBase = array(); 598 599 foreach($exportNSParts as $exportNS) 600 { 601 if ( $replaceParts[0] == $exportNS && $baseParts[0] == $exportNS ) 602 { 603 array_shift($replaceParts); 604 array_shift($baseParts); 605 array_shift($exportNSParts); 606 } 607 else { 608 // Nothing is matching anymore 609 break; 610 } 611 } 612 613 $i = count($exportNSParts); 614 $this->debug->message("Checking", array('current extra removing amount'=>$i,'replace'=>$replaceParts,'base'=>$baseParts,'exportNSParts'=>$exportNSParts),1); 615 616 // Now if there is just one item in the ens left and it matches the base, but not the replace, this miiiiiight be the case we want. 617 if ( count($exportNSParts) == 1 && $exportNSParts[0] == $baseParts[0] ) 618 { 619 array_shift($replaceParts); 620 $newBase = implode('/', $replaceParts); 621 622 if ( substr($newBase, -1) != '/' ) 623 { 624 $newBase .= '/'; 625 } 626 627 $this->debug->message("new Base: ", $newBase, 1); 628 629 // Now check from the beginning ... 630 return $newBase . $file; 631 } 632 633 return $origReplaceID; 634 } 635} 636 637?>