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