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