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