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