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