xref: /plugin/siteexport/inc/functions.php (revision a78c49a01a03138f9d9920f80ad45e644f045899)
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    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='&amp;', $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    /**
340     * @param integer $counter
341     */
342    function startRedirctProcess($counter) {
343        global $ID;
344
345        $URL = wl($ID);
346
347        $additionalParameters = $_REQUEST;
348        $additionalParameters['startcounter'] = $counter;
349        $additionalParameters['pattern'] = $this->settings->pattern;
350
351        unset($additionalParameters['id']);
352        unset($additionalParameters['u']);
353        unset($additionalParameters['p']);
354        unset($additionalParameters['r']);
355        unset($additionalParameters['http_credentials']);
356
357        $this->addAdditionalParametersToURL($URL, $additionalParameters);
358        $this->debug->message("Redirecting to '$URL'", null, 2);
359
360        send_redirect($URL);
361        exit(0); // Should not be reached, but anyways
362    }
363
364    /**
365     * Builds additional Parameters into the URL given
366     * @param $URL
367     * @param $newAdditionalParameters
368     */
369    function addAdditionalParametersToURL(&$URL, $newAdditionalParameters) {
370
371        // Add additionalParameters
372        if (!empty($newAdditionalParameters)) {
373            foreach ($newAdditionalParameters as $key => $value) {
374                if (empty($key) || empty($value)) { continue; }
375
376                $append = '';
377
378                if (is_array($value)) {
379                    foreach (array_values($value) as $aValue) { // Array Handling
380                        $URL .= (strstr($URL, '?') ? '&' : '?') . $key . "[]=$aValue";
381                    }
382                } else {
383                    $append = "$key=$value";
384                    $URL .= empty($append) || strstr($URL, $append) ? '' : (strstr($URL, '?') ? '&' : '?') . $append;
385                }
386
387            }
388        }
389    }
390
391    /**
392     * Cleans the wiki variables and returns a rebuild URL that has the new variables at hand
393     * @param $data
394     */
395    function prepare_POSTData($data)
396    {
397        $NS = !empty($data['ns']) ? $data['ns'] : $data['id'];
398
399        $this->removeWikiVariables($data);
400        $data['do'] = 'siteexport';
401        $additionalKeys = '';
402
403        ksort($data);
404
405        $this->debug->message("Prepared POST data:", $data, 1);
406
407        foreach ($data as $key => $value) {
408
409            if (!is_array($value)) { continue; }
410            $this->debug->message("Found inner Array:", $value, 1);
411
412            asort($value);
413            foreach ($value as $innerKey => $aValue)
414            {
415                if (is_numeric($innerKey))
416                {
417                    $innerKey = '';
418                }
419
420                $additionalKeys .= "&$key" . "[$innerKey]=$aValue";
421            }
422
423            unset($data[$key]);
424        }
425
426        return wl($NS, $data, true, '&') . $additionalKeys;
427    }
428
429    /**
430     * Parses a String into a $_REQUEST Like variant. You have to tell if a decode of the values is needed
431     * @param $inputArray
432     * @param $decode
433     */
434    public function parseStringToRequestArray($inputArray, $decode=false)
435    {
436        global $plugin_controller;
437
438        $outputArray = $inputArray;
439        if ( !is_array($inputArray) )
440        {
441            $intermediate = str_replace("&amp;", "&", $inputArray);
442
443            $outputArray = array();
444            foreach( explode("&", $intermediate) as $param ) {
445                list($key, $value) = explode("=", $param, 2);
446
447                // This is needed if we do want to calculate $_REQUEST for a non HTTP-Request
448                if ( $decode)
449                {
450                    $value = urldecode($value);
451                }
452
453                if ( empty($key) ) { continue; } // Don't check on Value, because there may be only the key that should be preserved
454
455                if ( substr($key, -2) == '[]' ) {
456                    $key = substr($key, 0, -2);
457                    if ( !is_array($outputArray[$key]) ) {
458                        $outputArray[$key] = array();
459                    }
460
461                    array_push($outputArray[$key], $value); // Array Handling
462                } else {
463                    $outputArray[$key] = $value;
464                }
465            }
466        }
467
468        if (!empty($outputArray['diPlu'])) {
469
470            $allPlugins = array();
471            foreach ($plugin_controller->getList(null, true) as $plugin) {
472                // check for CSS or JS
473                if (!file_exists(DOKU_PLUGIN . "$plugin/script.js") && !file_exists(DOKU_PLUGIN . "$p/style.css")) { continue; }
474                $allPlugins[] = $plugin;
475            }
476
477            if (count($outputArray['diPlu']) > (count($allPlugins)/2)) {
478                $outputArray['diInv'] = 1;
479                $outputArray['diPlu'] = array_diff($allPlugins, $outputArray['diPlu']);
480            }
481        }
482
483        return $outputArray;
484    }
485
486    /**
487     * Remove certain fields from the list.
488     * @param $removeArray
489     * @param $advanced
490     * @param $isString
491     */
492    function removeWikiVariables(&$removeArray, $advanced = false, $isString = false) {
493
494        $removeArray = $this->parseStringToRequestArray($removeArray);
495
496        // 2010-08-23 - If there is still the media set, retain the id for e.g. detail.php
497        if (!isset($removeArray['media'])) {
498            unset($removeArray['id']);
499        }
500
501        unset($removeArray['do']);
502        unset($removeArray['ns']);
503        unset($removeArray['call']);
504        unset($removeArray['sectok']);
505        unset($removeArray['rndval']);
506        unset($removeArray['tseed']);
507        unset($removeArray['http_credentials']);
508        unset($removeArray['u']);
509        unset($removeArray['p']);
510        unset($removeArray['r']);
511        unset($removeArray['base']);
512        unset($removeArray['siteexport']);
513        unset($removeArray['DokuWiki']);
514
515        if ($removeArray['renderer'] == 'xhtml') {
516            $removeArray['do'] = 'export_' . $removeArray['renderer'];
517            unset($removeArray['renderer']);
518        }
519
520        // Keep custom options
521        if (is_array($removeArray['customoptionname']) && is_array($removeArray['customoptionvalue']) && count($removeArray['customoptionname']) == count($removeArray['customoptionvalue']))
522        {
523            for ($index = 0; $index < count($removeArray['customoptionname']); $index++)
524            {
525                $removeArray[$removeArray['customoptionname'][$index]] = $removeArray['customoptionvalue'][$index];
526            }
527            unset($removeArray['customoptionname']);
528            unset($removeArray['customoptionvalue']);
529        }
530
531        if ($advanced) {
532            if ($removeArray['renderer'] != 'xhtml' && !empty($removeArray['renderer'])) {
533                $removeArray['do'] = 'export_' . $removeArray['renderer'];
534            }
535
536            // 2010-08-25 - Need fakeMedia for some _detail cases with rewrite = 2
537            if (isset($removeArray['fakeMedia'])) {
538                unset($removeArray['media']);
539                unset($removeArray['fakeMedia']);
540            }
541
542            /* remove internal params */
543            unset($removeArray['ens']);
544            unset($removeArray['renderer']);
545            unset($removeArray['site']);
546            unset($removeArray['namespace']);
547            unset($removeArray['exportbody']);
548            unset($removeArray['addParams']);
549            unset($removeArray['template']);
550            unset($removeArray['eclipseDocZip']);
551            unset($removeArray['useTocFile']);
552            unset($removeArray['JavaHelpDocZip']);
553            unset($removeArray['depth']);
554            unset($removeArray['depthType']);
555            unset($removeArray['startcounter']);
556            unset($removeArray['pattern']);
557            unset($removeArray['TOCMapWithoutTranslation']);
558            // unset($removeArray['disableCache']);
559            unset($removeArray['debug']);
560        }
561
562        if ($isString && is_array($removeArray)) {
563            $intermediate = $removeArray;
564            $removeArray = array();
565
566            foreach ($intermediate as $key => $value) {
567                if (is_array($value)) {
568                    foreach (array_values($value) as $aValue) { // Array Handling
569                        $removeArray[] = $key . "[]=$aValue";
570                    }
571                } else {
572                    $value = trim($value);
573
574                    $removeArray[] = "$key" . (((empty($value) && intval($value) !== 0)) || $value == '' ? '' : "=$value"); // If the Value is empty, the Key must be preserved
575                }
576            }
577
578            //$removeArray = implode( ($this->settings->fileType == 'pdf' ? "&" : "&amp;"), $removeArray);
579            $removeArray = implode("&", $removeArray); // The &amp; made problems with the HTTPClient / Apache. It should not be a problem to have &
580        }
581    }
582
583    /**
584     * returns a hashed name for the parameters
585     * @param $parameters
586     */
587    public function hashNameForParameters($parameters)
588    {
589        return md5($parameters);
590    }
591
592    /**
593     * Takes an URL and transforms it into the path+query part
594     * Used several times, e.g. for genering the hash for the cache file
595     * @param string $url
596     */
597    public function urlToPathAndParams($url)
598    {
599        $query = parse_url($url, PHP_URL_QUERY);
600        $path = preg_replace(":^" . DOKU_REL . ":", "", parse_url($url, PHP_URL_PATH));
601        return "{$path}?{$query}";
602    }
603
604    /**
605     * Transforms an $_REQUEST into a Hash that can be used for cron and cache file
606     * @param $request
607     */
608    public function requestParametersToCacheHash($request)
609    {
610        $params = $this->urlToPathAndParams($this->prepare_POSTData($request));
611        $this->debug->message("Calculated the following Cache Hash URL: ", $params, 2);
612        return $this->hashNameForParameters($params);
613    }
614
615    /**
616     * Check a replaceURL against a baseURL - and make the replaceURL relative against it
617     * @param replaceURL - URL which will be made relative if needed
618     * @param baseURL - URL which is the reference to be made relative against
619     * @param string $replaceURL
620     */
621    public function getRelativeURL($replaceURL, $baseURL, $existingPageID = null)
622    {
623        // Base is always absolute without anything at the beginning
624        if (preg_match("#^(\.\./)+#", $baseURL)) {
625            $this->debug->message("The baseURL was not absolute.", $baseURL, 1);
626            return $replaceURL;
627        }
628
629        $origReplaceURL = $replaceURL;
630        $replaceURL = preg_replace("#^(\.\./)+#", '', $replaceURL);
631
632        // Remove ../ at beginning to get the absolute path
633        if ($replaceURL == $origReplaceURL) {
634            $this->debug->message("The replaceURL was already absolute.", $replaceURL, 1);
635            return $replaceURL;
636        }
637
638        $replaceParts = explode('/', $replaceURL);
639        $fileName = array_pop($replaceParts); // Get file
640
641        $baseParts = explode('/', $baseURL);
642        array_pop($baseParts); // Remove file. We only need the path to this location.
643
644        $this->debug->message("State before kicking.", array($replaceParts, $baseParts), 1);
645
646        // Kick all ../
647        $originalBasePartsCount = count($baseParts);
648        while (count($replaceParts) > 0 && count($baseParts) > 0) {
649
650            if ($baseParts[0] == $replaceParts[0]) {
651                // Beginning is OK, so remove it.
652                array_shift($replaceParts);
653                array_shift($baseParts);
654            } else {
655                break;
656            }
657
658        }
659
660        $this->debug->message("Found URL '{$replaceURL}' that is relative to current page '{$baseURL}'.", array($replaceParts, $baseParts), 1);
661
662        // Remove everything that is identical
663        $replaceParts[] = $fileName;
664
665        // do the final link calculation
666        $finalLink = str_repeat('../', count($baseParts)) . implode('/', $replaceParts);
667
668        // find out if this is outside of our own export context, becond the baseURL
669        $offsiteTemplate = $this->getConf("offSiteLinkTemplate");
670        if (count($baseParts) == $originalBasePartsCount && $existingPageID != null && !empty($offsiteTemplate)) {
671
672            $offsiteTemplate = str_replace('RAWID', $existingPageID, $offsiteTemplate);
673
674            $check = null;
675            $offsiteTemplate = str_replace('CONTEXTID', array_pop($this->getMapID($existingPageID, null, $check)), $offsiteTemplate);
676            $offsiteTemplate = str_replace('LINK', $finalLink, $offsiteTemplate);
677
678            $finalLink = $$offsiteTemplate;
679        }
680
681        return $finalLink;
682    }
683
684    function mapIDWithAnchor(&$n, $key, $postfix)
685    {
686        if (empty($postfix)) return;
687        $n .= '-' . $postfix;
688    }
689
690    function getMapID($elemID, $postfix, &$check)
691    {
692        $meta = p_get_metadata($elemID, 'context', true);
693
694        if (empty($meta['id'])) {
695            $title = empty($meta['title']) ? $this->getSiteTitle($elemID) : $meta['title'];
696            $meta['id'] = sectionID($this->cleanId(strtolower($title)), $check);
697        }
698
699        $mapID = explode('|', $meta['id']);
700        array_walk($mapID, array($this, 'mapIDWithAnchor'), $postfix);
701
702        return $mapID;
703    }
704
705    public function hasAuthentication() {
706        $user = $this->getConf('defaultAuthenticationUser');
707        $password = $this->getConf('defaultAuthenticationPassword');
708        return empty($user) ? false : array(
709            'user' => $user,
710            'password' => $password
711        );
712    }
713
714    public function authenticate() {
715        if (!isset($_SERVER['HTTP_AUTHORIZATION']) && $this->hasAuthentication()) {
716            $authentication = $this->hasAuthentication();
717            $_SERVER['HTTP_AUTHORIZATION'] = 'Basic ' . base64_encode($authentication['user'] . ':' . $authentication['password']);
718            $this->debug->message("Re-authenticating with default user from configuration", $authentication['user'], 3);
719            return auth_setup();
720        }
721
722        return false;
723    }
724
725    /**
726     * Check the secret CSRF token, regardless of the current authorization
727     *
728     * @param null|string $token security token
729     * @param null|boolean $softfail if a message is to be thrown.
730     * @return bool success if the token matched
731     */
732    function checkSecurityToken($token = null, $softfail = true) {
733        /** @var Input $INPUT */
734        $secToken = $this->getSecurityToken();
735        if ( empty( $secToken) && empty ( $token ) ) return false;
736        if($secToken != $token) {
737            if ( !$softfail ) msg('Security Token did not match. Possible CSRF attack.', -1);
738            return false;
739        }
740        return true;
741    }
742
743    /**
744     * Return a secret token to be used for CSRF attack prevention
745     * This is known to be flawed by default
746     *
747     * @author  Andreas Gohr <andi@splitbrain.org>
748     * @link    http://en.wikipedia.org/wiki/Cross-site_request_forgery
749     * @link    http://christ1an.blogspot.com/2007/04/preventing-csrf-efficiently.html
750     * @link    https://github.com/splitbrain/dokuwiki/issues/1883
751     *
752     * @return  string
753     */
754     function getSecurityToken() {
755        /** @var Input $INPUT */
756        global $INPUT;
757        return PassHash::hmac('md5', session_id().'siteexport', 'siteexport_salt'.auth_cookiesalt());
758    }
759}
760