xref: /plugin/siteexport/inc/functions.php (revision f161d4fba03cb6d24d9cc13577cad50481efcf26)
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
7use dokuwiki\File\PageResolver;
8
9class siteexport_functions extends DokuWiki_Plugin
10{
11    public $debug = null;
12    public $settings = null;
13
14    public function __construct($init = true, $isAJAX = false)
15    {
16        if ($init)
17        {
18            $this->debug = new siteexport_debug();
19            $this->debug->isAJAX = $isAJAX;
20
21            $this->settings = new settings_plugin_siteexport_settings($this);
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        $params = array('cache' => 'nocache', 'siteexport' => $this->settings->pattern);
34
35        if ($this->debug->debugLevel() < 5) {
36            // If debug, then debug!
37            $params['debug'] = $this->debug->debugLevel();
38        }
39
40        return ml($this->settings->origZipFile, $params, true, '&');
41    }
42
43    public function checkIfCacheFileExistsForFileWithPattern($file, $pattern)
44    {
45        if (!@file_exists($file))
46        {
47            // If the cache File does not exist, move the newly created one over ...
48            $this->debug->message("'{$file}' does not exist. Checking original ZipFile", null, 3);
49            $newCacheFile = mediaFN($this->getSpecialExportFileName($this->settings->origZipFile, $pattern));
50
51            if (!@file_exists($newCacheFile))
52            {
53                $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);
54            }
55
56            $status = io_rename($newCacheFile, $file);
57            $this->debug->message("had to move another original file over. Did it work? " . ($status ? 'Yes, it did.' : 'No, it did not.'), null, 2);
58        } else {
59            $this->debug->message("The file does exist!", $file, 2);
60        }
61    }
62
63
64    /**
65     * Returns an utf8 encoded Namespace for a Page and input Namespace
66     * @param $NS
67     * @param $PAGE
68     */
69    public function getNamespaceFromID($NS, &$PAGE) {
70        global $conf;
71        // Check current page - if its an NS add the startpage
72        $NS = (new PageResolver($NS))->resolveId($NS);
73        $NSa = explode(':', $NS);
74        if (!page_exists($NS) && array_pop($NSa) != strtolower($conf['start'])) { // Compare to lowercase since clean lowers it.
75            $NS .= ':' . $conf['start'];
76            $NS = (new PageResolver($NS))->resolveId($NS);
77        }
78
79        $PAGE = noNS($NS);
80        $NS = getNS($NS);
81
82        return utf8_encodeFN(str_replace(':', '/', $NS));
83    }
84
85    /**
86     * create a file name for the page
87     **/
88    public function getSiteName($ID, $overrideRewrite = false) {
89        global $conf;
90
91        if (empty($ID)) return false;
92
93        // Remove extensions
94        if ($overrideRewrite) {
95            $ID = preg_replace("#\.(php|html)$#", '', $ID);
96        }
97
98        $url = $this->wl($this->cleanID($ID), null, true, null, null, $overrideRewrite); // 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 = null;
113            if (class_exists('siteexport_pdfgenerator')) {
114                $heading = p_get_metadata(cleanID($ID),'pdftitle',METADATA_RENDER_USING_SIMPLE_CACHE);
115            }
116            $heading = empty($heading) ? p_get_metadata(cleanID($ID),'breadtitle',METADATA_RENDER_USING_SIMPLE_CACHE) : $heading;
117            $heading = empty($heading) ? p_get_first_heading($ID, true) : $heading;
118            if ($heading) {
119                return $this->xmlEntities($heading);
120            }
121        }
122        $elements = explode(':', $ID);
123        return ucwords($this->xmlEntities(array_pop($elements)));
124    }
125
126    /**
127     * Encoding ()taken from DW - but without needing the renderer
128     **/
129    public function xmlEntities($string) {
130        return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
131    }
132
133    /**
134     * Create name for the file inside the zip and the replacements
135     **/
136    public function shortenName($NAME)
137    {
138        $NS = $this->settings->exportNamespace;
139        $NAME = preg_replace("%^" . preg_quote(DOKU_BASE, '%') . "%", "", $NAME);
140        $NAME = preg_replace("%^((_media|_detail)/)?(" . preg_quote($NS, '%') . "/)?%", "", $NAME);
141
142        if (strstr($NAME, '%')) { $NAME = rawurldecode($NAME); }
143
144        $this->debug->message("Shortening file to '$NAME'", null, 1);
145        return $NAME;
146    }
147
148    /**
149     * Remove unwanted chars from ID
150     *
151     * Cleans a given ID to only use allowed characters. Accented characters are
152     * converted to unaccented ones
153     *
154     * @author Andreas Gohr <andi@splitbrain.org>
155     * @param  string  $raw_id    The pageid to clean
156     * @param  boolean $ascii     Force ASCII
157     * @param  boolean $media     Allow leading or trailing _ for media files
158     */
159    public function cleanID($raw_id, $ascii = false, $media = false) {
160        global $conf;
161        global $lang;
162        static $sepcharpat = null;
163
164        global $cache_cleanid;
165        $cache = & $cache_cleanid;
166
167        // check if it's already in the memory cache
168        if (isset($cache[(string) $raw_id])) {
169            return $cache[(string) $raw_id];
170        }
171
172        $sepchar = $conf['sepchar'];
173        if ($sepcharpat == null) // build string only once to save clock cycles
174        $sepcharpat = '#\\' . $sepchar . '+#';
175
176        $id = trim((string) $raw_id);
177        // NO LowerCase for us! - Preserve it, that is why the call is missing here.
178
179        //alternative namespace seperator
180        $id = strtr($id, ';', ':');
181        if ($conf['useslash']) {
182            $id = strtr($id, '/', ':');
183        } else {
184            $id = strtr($id, '/', $sepchar);
185        }
186
187        if ($conf['deaccent'] == 2 || $ascii) $id = utf8_romanize($id);
188        if ($conf['deaccent'] || $ascii) $id = utf8_deaccent($id, -1);
189
190        // We want spaces to be preserved when they are in the link.
191        global $UTF8_SPECIAL_CHARS2;
192        $UTF8_SPECIAL_CHARS2_SAVE = (string) $UTF8_SPECIAL_CHARS2;
193        $UTF8_SPECIAL_CHARS2 = str_replace(' ', '', $UTF8_SPECIAL_CHARS2);
194
195        //remove specials
196        $id = utf8_stripspecials($id, $sepchar, '\*');
197        $UTF8_SPECIAL_CHARS2 = $UTF8_SPECIAL_CHARS2_SAVE;
198
199        if ($ascii) $id = utf8_strip($id);
200
201        //clean up
202        $id = preg_replace($sepcharpat, $sepchar, $id);
203        $id = preg_replace('#:+#', ':', $id);
204        $id = ($media ? trim($id, ':.-') : trim($id, ':._-'));
205        $id = preg_replace('#:[:\._\-]+#', ':', $id);
206
207        $cache[(string) $raw_id] = $id;
208        return($id);
209    }
210
211
212    /**
213     * This builds a link to a wikipage - changed for internal use here
214     *
215     * It handles URL rewriting and adds additional parameter if
216     * given in $more
217     *
218     * @author Andreas Gohr <andi@splitbrain.org>
219     */
220
221    public function wl($id='',$more='',$abs=false,$sep='&amp;', $IDexists=true, $overrideRewrite=false, $hadBase=false){
222        global $conf;
223
224        $this->debug->message("Starting to build WL-URL for '$id'", $more, 1);
225
226        if(is_array($more)){
227
228            $intermediateMore = '';
229            foreach( $more as $key => $value) {
230
231                if ( strlen($intermediateMore) > 0 ) {
232                    $intermediateMore .= $sep;
233                }
234
235                if ( !is_array($value) ) {
236                    $intermediateMore .= rawurlencode($key) . '=';
237                    $intermediateMore .= rawurlencode($value);
238                    continue;
239                }
240
241                foreach( $value as $val ) {
242                    if ( strlen($intermediateMore) > 0 ) {
243                        $intermediateMore .= $sep;
244                    }
245
246                    $intermediateMore .= rawurlencode($key) . '[]=';
247                    $intermediateMore .= rawurlencode($val);
248                }
249            }
250
251            $more = $intermediateMore;
252        } else {
253            $more = str_replace(',', $sep, $more);
254        }
255
256        $id = idfilter($id);
257
258        if ($abs) {
259            $xlink = DOKU_URL;
260            if (!$IDexists && !$hadBase) { // If the file does not exist, we have to remove the base. This link my be one to an parallel BASE.
261                $xlink = preg_replace('#' . DOKU_BASE . '$#', '', $xlink);
262            }
263        } else if ($IDexists || $hadBase) { // if the ID does exist, we may add the base.
264            $xlink = DOKU_BASE;
265        } else {
266            $xlink = "";
267        }
268
269        // $this->debug->message("internal WL function Before Replacing: '$xlink'", array(DOKU_REL, DOKU_URL, DOKU_BASE, $xlink), 2);
270        $xlink = preg_replace('#(?<!http:|https:)//+#', '/', ($abs ? '' : '/') . "$xlink/"); // ensure slashes at beginning and ending, but strip doubles
271        $this->debug->message("'$xlink'", array(DOKU_REL, DOKU_URL, DOKU_BASE, $xlink), 2);
272
273        if ($overrideRewrite) {
274            $this->debug->message("Override enabled.", null, 1);
275            $id = strtr($id, ':', '/');
276
277            $xlink .= $id;
278            if ($more) $xlink .= '?' . $more;
279        } else {
280            if ($conf['userewrite'] == 2) {
281                $xlink .= DOKU_SCRIPT . '/' . $id;
282                if ($more) $xlink .= '?' . $more;
283            }elseif ($conf['userewrite']) {
284                $xlink .= $id;
285                if ($more) $xlink .= '?' . $more;
286            }elseif ($id) {
287                $xlink .= DOKU_SCRIPT . '?id=' . $id;
288                if ($more) $xlink .= $sep . $more;
289            } else {
290                $xlink .= DOKU_SCRIPT;
291                if ($more) $xlink .= '?' . $more;
292            }
293        }
294
295        $this->debug->message("internal WL function result: '$xlink'", null, 2);
296
297        return $xlink;
298    }
299
300    /**
301     * Create the export file name - this is the file where everything is being stored
302     * @param $FILE String name of the file
303     * @param $PATTERN String additional pattern for re-using old files
304     */
305    public function getSpecialExportFileName($FILE, $PATTERN = null) {
306
307        if (empty($FILE))
308        {
309            $FILE = $this->settings->origZipFile;
310        }
311
312        if (empty($PATTERN) && empty($this->settings->pattern)) {
313            $this->debug->message("Generating an internal md5 pattern. This will go wrong - and won't cache properly.", null, 3);
314            $PATTERN = md5(microtime(false));
315        }
316
317        // Set Pattern Global for other stuff
318        if (empty($this->settings->pattern)) {
319            $this->settings['pattern'] = $PATTERN;
320        } else {
321            $PATTERN = $this->settings->pattern;
322        }
323
324        $FA = explode('.', $FILE);
325        $EXT = array_pop($FA);
326        array_push($FA, 'auto');
327        array_push($FA, $PATTERN);
328        array_push($FA, $EXT);
329
330        $fileName = implode('.', $FA);
331        $this->debug->message("Export Filename for '$FILE' will be: '$fileName'", null, 2);
332        return $fileName;
333    }
334
335    public function getCacheFileNameForPattern($PATTERN = null)
336    {
337        if ($PATTERN == null) {
338            $PATTERN = $this->settings->pattern;
339        }
340
341        return getCacheName($this->getSpecialExportFileName($this->settings->origZipFile, $PATTERN), '.' . basename(mediaFN($this->settings->origZipFile)));
342    }
343
344    /**
345     * @param integer $counter
346     */
347    public function startRedirctProcess($counter) {
348        global $ID;
349
350        $URL = wl($ID);
351
352        $additionalParameters = $_REQUEST;
353        $additionalParameters['startcounter'] = $counter;
354        $additionalParameters['pattern'] = $this->settings->pattern;
355
356        unset($additionalParameters['id']);
357        unset($additionalParameters['u']);
358        unset($additionalParameters['p']);
359        unset($additionalParameters['r']);
360        unset($additionalParameters['http_credentials']);
361
362        $this->addAdditionalParametersToURL($URL, $additionalParameters);
363        $this->debug->message("Redirecting to '$URL'", null, 2);
364
365        send_redirect($URL);
366        exit(0); // Should not be reached, but anyways
367    }
368
369    /**
370     * Builds additional Parameters into the URL given
371     * @param $URL
372     * @param $newAdditionalParameters
373     */
374    public function addAdditionalParametersToURL(&$URL, $newAdditionalParameters) {
375
376        // Add additionalParameters
377        if (!empty($newAdditionalParameters)) {
378            foreach ($newAdditionalParameters as $key => $value) {
379                if (empty($key) || empty($value)) { continue; }
380
381                if (is_array($value)) {
382                    foreach (array_values($value) as $aValue) { // Array Handling
383                        $URL .= (strstr($URL, '?') ? '&' : '?') . $key . "[]=$aValue";
384                    }
385                } else {
386                    $append = "$key=$value";
387                    $URL .= empty($append) || strstr($URL, $append) ? '' : (strstr($URL, '?') ? '&' : '?') . $append;
388                }
389            }
390        }
391    }
392
393    /**
394     * Cleans the wiki variables and returns a rebuild URL that has the new variables at hand
395     * @param $data
396     */
397    public function prepare_POSTData($data)
398    {
399        $NS = array_key_exists( 'ns', $data ) ? $data['ns'] : ( array_key_exists( 'id', $data ) ? $data['id'] : ':' );
400
401        $this->removeWikiVariables($data);
402        $data['do'] = 'siteexport';
403        $additionalKeys = '';
404
405        ksort($data);
406
407        $this->debug->message("Prepared POST data:", $data, 1);
408
409        foreach ($data as $key => $value) {
410
411            if (!is_array($value)) { continue; }
412            $this->debug->message("Found inner Array:", $value, 1);
413
414            asort($value);
415            foreach ($value as $innerKey => $aValue)
416            {
417                if (is_numeric($innerKey))
418                {
419                    $innerKey = '';
420                }
421
422                $additionalKeys .= "&$key" . "[$innerKey]=$aValue";
423            }
424
425            unset($data[$key]);
426        }
427
428        return wl($NS, $data, true, '&') . $additionalKeys;
429    }
430
431    /**
432     * Parses a String into a $_REQUEST Like variant. You have to tell if a decode of the values is needed
433     * @param $inputArray
434     * @param $decode
435     */
436    public function parseStringToRequestArray($inputArray, $decode=false)
437    {
438        global $plugin_controller;
439
440        $outputArray = $inputArray;
441        if ( !is_array($inputArray) )
442        {
443            if (empty($inputArray))
444                return array();
445
446            $intermediate = str_replace("&amp;", "&", $inputArray);
447
448            $outputArray = array();
449            foreach( explode("&", $intermediate) as $param ) {
450                list($key, $value) = array_pad( explode("=", $param, 2), 2, null );
451
452                // This is needed if we do want to calculate $_REQUEST for a non HTTP-Request
453                if ( $decode)
454                {
455                    $value = urldecode($value);
456                }
457
458                if ( empty($key) ) { continue; } // Don't check on Value, because there may be only the key that should be preserved
459
460                if ( substr($key, -2) == '[]' ) {
461                    $key = substr($key, 0, -2);
462                    if ( !is_array($outputArray[$key]) ) {
463                        $outputArray[$key] = array();
464                    }
465
466                    array_push($outputArray[$key], $value); // Array Handling
467                } else {
468                    $outputArray[$key] = $value;
469                }
470            }
471        }
472
473        if (!empty($outputArray['diPlu'])) {
474
475            $allPlugins = array();
476            foreach ($plugin_controller->getList(null, true) as $plugin) {
477                // check for CSS or JS
478                if (!file_exists(DOKU_PLUGIN . $plugin . "/script.js") && !file_exists(DOKU_PLUGIN . $plugin . "/style.css")) { continue; }
479                $allPlugins[] = $plugin;
480            }
481
482            if (count($outputArray['diPlu']) > (count($allPlugins)/2)) {
483                $outputArray['diInv'] = 1;
484                $outputArray['diPlu'] = array_diff($allPlugins, $outputArray['diPlu']);
485            }
486        }
487
488        return $outputArray;
489    }
490
491    /**
492     * Remove certain fields from the list.
493     * @param $removeArray
494     * @param $advanced
495     * @param $isString
496     */
497    public function removeWikiVariables(&$removeArray, $advanced = false, $isString = false) {
498
499        $removeArray = $this->parseStringToRequestArray($removeArray);
500        $removeKeys = array();
501
502        // 2010-08-23 - If there is still the media set, retain the id for e.g. detail.php
503        if (!isset($removeArray['media'])) {
504            $removeKeys[] = 'id';
505        }
506
507        unset($removeArray['do']);
508        $removeKeys[] = 'ns';
509        $removeKeys[] = 'call';
510        $removeKeys[] = 'sectok';
511        $removeKeys[] = 'rndval';
512        $removeKeys[] = 'tseed';
513        $removeKeys[] = 'http_credentials';
514        $removeKeys[] = 'u';
515        $removeKeys[] = 'p';
516        $removeKeys[] = 'r';
517        $removeKeys[] = 'base';
518        $removeKeys[] = 'siteexport';
519        $removeKeys[] = 'DokuWiki';
520
521        if ( array_key_exists( 'renderer', $removeArray ) && $removeArray['renderer'] == 'xhtml') {
522            $removeArray['do'] = 'export_' . $removeArray['renderer'];
523            $removeKeys[] = 'renderer';
524        }
525
526        // Keep custom options
527        if ( array_key_exists( 'customoptionname', $removeArray ) &&  is_array($removeArray['customoptionname']) && is_array($removeArray['customoptionvalue']) && count($removeArray['customoptionname']) == count($removeArray['customoptionvalue']))
528        {
529            for ($index = count($removeArray['customoptionname']); $index >= 0; $index--)
530            {
531                $removeArray[$removeArray['customoptionname'][$index]] = $removeArray['customoptionvalue'][$index];
532            }
533            $removeKeys[] = 'customoptionname';
534            $removeKeys[] = 'customoptionvalue';
535        }
536
537        if ($advanced) {
538            if ( array_key_exists( 'renderer', $removeArray ) && $removeArray['renderer'] != 'xhtml' && !empty($removeArray['renderer'])) {
539                $removeArray['do'] = 'export_' . $removeArray['renderer'];
540            }
541
542            // 2010-08-25 - Need fakeMedia for some _detail cases with rewrite = 2
543            if (isset($removeArray['fakeMedia'])) {
544                $removeKeys[] = 'media';
545                $removeKeys[] = 'fakeMedia';
546            }
547
548            /* remove internal params */
549            $removeKeys[] = 'ens';
550            $removeKeys[] = 'renderer';
551            $removeKeys[] = 'site';
552            $removeKeys[] = 'namespace';
553            $removeKeys[] = 'exportbody';
554            $removeKeys[] = 'addParams';
555            $removeKeys[] = 'template';
556            $removeKeys[] = 'eclipseDocZip';
557            $removeKeys[] = 'useTocFile';
558            $removeKeys[] = 'JavaHelpDocZip';
559            $removeKeys[] = 'depth';
560            $removeKeys[] = 'depthType';
561            $removeKeys[] = 'startcounter';
562            $removeKeys[] = 'pattern';
563            $removeKeys[] = 'TOCMapWithoutTranslation';
564
565            $removeKeys[] = 'debug';
566        }
567
568        foreach($removeKeys as $key) {
569            unset($removeArray[$key]);
570        }
571
572        if ($isString && is_array($removeArray)) {
573            $intermediate = $removeArray;
574            $removeArray = array();
575
576            foreach ($intermediate as $key => $value) {
577                if (is_array($value)) {
578                    foreach (array_values($value) as $aValue) { // Array Handling
579                        $removeArray[] = $key . "[]=$aValue";
580                    }
581                } else {
582                    $value = trim($value);
583
584                    $removeArray[] = "$key" . (((empty($value) && intval($value) !== 0)) || $value == '' ? '' : "=$value"); // If the Value is empty, the Key must be preserved
585                }
586            }
587
588            $removeArray = implode("&", $removeArray); // The &amp; made problems with the HTTPClient / Apache. It should not be a problem to have &
589        }
590    }
591
592    /**
593     * returns a hashed name for the parameters
594     * @param $parameters
595     */
596    public function hashNameForParameters($parameters)
597    {
598        return md5($parameters);
599    }
600
601    /**
602     * Takes an URL and transforms it into the path+query part
603     * Used several times, e.g. for genering the hash for the cache file
604     * @param string $url
605     */
606    public function urlToPathAndParams($url)
607    {
608        $query = parse_url($url, PHP_URL_QUERY);
609        $path = preg_replace(":^" . DOKU_REL . ":", "", parse_url($url, PHP_URL_PATH));
610        return "{$path}?{$query}";
611    }
612
613    /**
614     * Transforms an $_REQUEST into a Hash that can be used for cron and cache file
615     * @param $request
616     */
617    public function requestParametersToCacheHash($request)
618    {
619        $params = $this->urlToPathAndParams($this->prepare_POSTData($request));
620        $this->debug->message("Calculated the following Cache Hash URL: ", $params, 2);
621        return $this->hashNameForParameters($params);
622    }
623
624    /**
625     * Check a replaceURL against a baseURL - and make the replaceURL relative against it
626     * @param replaceURL String URL which will be made relative if needed
627     * @param baseURL String URL which is the reference to be made relative against
628     * @param existingPageID Array
629     */
630    public function getRelativeURL($replaceURL, $baseURL, $existingPageID = null)
631    {
632        // Base is always absolute without anything at the beginning
633        if (preg_match("#^(\.\./)+#", $baseURL)) {
634            $this->debug->message("The baseURL was not absolute.", $baseURL, 1);
635            return $replaceURL;
636        }
637
638        $origReplaceURL = $replaceURL;
639        $replaceURL = preg_replace("#^(\.\./)+#", '', $replaceURL);
640
641        // Remove ../ at beginning to get the absolute path
642        if ($replaceURL == $origReplaceURL) {
643            $this->debug->message("The replaceURL was already absolute.", $replaceURL, 1);
644            return $replaceURL;
645        }
646
647        $replaceParts = explode('/', $replaceURL);
648        $fileName = array_pop($replaceParts); // Get file
649
650        $baseParts = explode('/', $baseURL);
651        array_pop($baseParts); // Remove file. We only need the path to this location.
652
653        $this->debug->message("State before kicking.", array($replaceParts, $baseParts), 1);
654
655        // Kick all ../
656        $originalBasePartsCount = count($baseParts);
657        $didKickSomeParts = 0; // true means, that some parts of the base URL were identical
658        while (count($replaceParts) > 0 && count($baseParts) > 0) {
659
660            if ($baseParts[0] == $replaceParts[0]) {
661                // Beginning is OK, so remove it.
662                array_shift($replaceParts);
663                array_shift($baseParts);
664                $didKickSomeParts++;
665            } else {
666                break;
667            }
668
669        }
670
671        $this->debug->message("Found URL '{$replaceURL}' that is relative to current page '{$baseURL}'.", array($replaceParts, $baseParts), 1);
672
673        // Remove everything that is identical
674        $replaceParts[] = $fileName;
675
676        // do the final link calculation
677        $finalLink = str_repeat('../', count($baseParts)) . implode('/', $replaceParts);
678
679        // Means nothing was kicked, so other plugin
680        $isExternalPage = count($baseParts) == $originalBasePartsCount;
681
682        // the new page is in the same plugin, with a different subcontext and same language
683        $parts0equal = (isset($baseParts[0]) && isset($replaceParts[0]) && $baseParts[0] == $replaceParts[0]);
684        $parts1equal = (isset($baseParts[1]) && isset($replaceParts[1]) && $baseParts[1] == $replaceParts[1]);
685        $isExternalPage = $isExternalPage || ($didKickSomeParts == 1 && !$parts0equal && $parts1equal);
686
687        // find out if this is outside of our own export context, beyond the baseURL
688        $offsiteTemplate = $this->getConf("offSiteLinkTemplate");
689        $this->debug->message("Checking for offsite links", array(
690            "baseParts" => count($baseParts),
691            "originalBaseParts" => $originalBasePartsCount,
692            "ExistingPageID" => $existingPageID,
693            "finalLink" => $finalLink,
694            "offsiteTemplate" => $offsiteTemplate,
695            "isExternalPage" => $isExternalPage,
696            "didKickSomeParts" => $didKickSomeParts
697
698        ), 1);
699
700        if ( $isExternalPage && $existingPageID != null && !empty($offsiteTemplate)) {
701
702            $offsiteTemplate = str_replace('RAWID', $existingPageID, $offsiteTemplate);
703
704            $check = null;
705            $mapID = $this->getMapID($existingPageID, null, $check);
706            $offsiteTemplate = str_replace('CONTEXTID', array_pop($mapID), $offsiteTemplate);
707            $offsiteTemplate = str_replace('LINK', $finalLink, $offsiteTemplate);
708
709            $this->debug->message("Replacing finalLink '${finalLink}' with offsiteLink '${offsiteTemplate}'", null, 1);
710            $finalLink = $offsiteTemplate;
711        }
712
713        return $finalLink;
714    }
715
716    public function mapIDWithAnchor(&$n, $key, $postfix)
717    {
718        if (empty($postfix)) return;
719        $n .= '-' . $postfix;
720    }
721
722    public function getMapID($elemID, $postfix, &$check)
723    {
724        $meta = p_get_metadata($elemID, 'context', true);
725
726        if (empty($meta['id'])) {
727            $title = empty($meta['title']) ? $this->getSiteTitle($elemID) : $meta['title'];
728            $meta['id'] = sectionID($this->cleanId($title), $check);
729        }
730
731        $mapID = explode('|', $meta['id']);
732        array_walk($mapID, array($this, 'mapIDWithAnchor'), $postfix);
733
734        return $mapID;
735    }
736
737    public function hasAuthentication() {
738        $user = $this->getConf('defaultAuthenticationUser');
739        $password = $this->getConf('defaultAuthenticationPassword');
740        return empty($user) ? false : array(
741            'user' => $user,
742            'password' => $password
743        );
744    }
745
746    public function authenticate() {
747        if (!isset($_SERVER['HTTP_AUTHORIZATION']) && $this->hasAuthentication()) {
748            $authentication = $this->hasAuthentication();
749            $_SERVER['HTTP_AUTHORIZATION'] = 'Basic ' . base64_encode($authentication['user'] . ':' . $authentication['password']);
750            $this->debug->message("Re-authenticating with default user from configuration", $authentication['user'], 3);
751            return auth_setup();
752        }
753
754        return false;
755    }
756
757    /**
758     * Check the secret CSRF token, regardless of the current authorization
759     *
760     * @param null|string $token security token
761     * @param null|boolean $softfail if a message is to be thrown.
762     * @return bool success if the token matched
763     */
764    public function checkSecurityToken($token = null, $softfail = true) {
765        /** @var Input $INPUT */
766        $secToken = $this->getSecurityToken();
767        if ( empty( $secToken) && empty ( $token ) ) return false;
768        if($secToken != $token) {
769            if ( $softfail !== true ) msg('Security Token did not match. Possible CSRF attack.', -1);
770            return false;
771        }
772        return true;
773    }
774
775    /**
776     * Return a secret token to be used for CSRF attack prevention
777     * This is known to be flawed by default
778     *
779     * @author  Andreas Gohr <andi@splitbrain.org>
780     * @link    http://en.wikipedia.org/wiki/Cross-site_request_forgery
781     * @link    http://christ1an.blogspot.com/2007/04/preventing-csrf-efficiently.html
782     * @link    https://github.com/splitbrain/dokuwiki/issues/1883
783     *
784     * @return  string
785     */
786     public function getSecurityToken() {
787        /** @var Input $INPUT */
788        global $INPUT;
789        return PassHash::hmac('md5', session_id().'siteexport', 'siteexport_salt'.auth_cookiesalt());
790    }
791}
792