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