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