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