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