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