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