1<?php
2/**
3 * Imports filetrees into DokuWikis media directory
4 * and creating a wiki pagetree with filelists linking to the
5 * imported documents
6 *
7 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
8 * @author     Frank Schiebel <frank@linuxmuster.net>
9 */
10
11if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
12if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
13if(!defined('DOKU_INCLUDE')) define('DOKU_INCLUDE',DOKU_INC.'inc/');
14require_once(DOKU_PLUGIN . 'admin.php');
15require_once(DOKU_INCLUDE . 'io.php');
16
17/**
18 * All DokuWiki plugins to extend the admin function
19 * need to inherit from this class
20 */
21class admin_plugin_doctree2filelist extends DokuWiki_Admin_Plugin
22{
23    /**
24     * Constructor
25     */
26    function admin_plugin_doctree2filelist()
27    {
28        $this->setupLocale();
29    }
30
31    /**
32     * return some info
33     */
34    function getInfo()
35    {
36        return array(
37            'author' => 'Frank Schiebel',
38            'email'  => 'frank@linuxmuster.net',
39            'date'   => '2010-11-27',
40            'name'   => 'doctree2filelist: Imports document tree into dokuwiki',
41            'desc'   => 'This plugin is for importing a whole tree with (office-)documents to a wiki page-structure. It has been written for openschulportfolio, a dokuwiki based portfolio-system for schools.',
42            'url'    => 'http://www.openschulportfolio.de/',
43        );
44    }
45
46    /**
47     * return sort order for position in admin menu
48     */
49    function getMenuSort()
50    {
51        return 999;
52    }
53
54    /**
55     *  return a menu prompt for the admin menu
56     *  NOT REQUIRED - its better to place $lang['menu'] string in localised string file
57     *  only use this function when you need to vary the string returned
58     */
59    function getMenuText()
60    {
61        $menu_base = $this->getLang('plugname');
62        return $menu_base;
63    }
64
65    /**
66     * handle user request
67     */
68    function handle() {
69        if (!isset($_REQUEST['ospcmd'])) return;
70
71        if ($_REQUEST['ospcmd'] == "create_upload_dir") {
72            $this->_create_upload_dir();
73        }
74        if ($_REQUEST['ospcmd'] == "docsuploaded") {
75            $this->_save_status("DOCSUPLOADED");
76        }
77        if ($_REQUEST['ospcmd'] == "delete_upload_dir") {
78            $this->_delete_upload_dir();
79        }
80        if ($_REQUEST['ospcmd'] == "start_over") {
81            $this->_reset_wizard();
82        }
83        if ($_REQUEST['ospcmd'] == "importit") {
84            $this->_import_docs();
85        }
86    }
87
88    /**
89     * output appropriate html
90     */
91    function html() {
92        global $conf;
93
94        # check for filelist plugin
95        if (!file_exists(DOKU_PLUGIN . "filelist/syntax.php")) {
96            print $this->_div_warning("start");
97            print $this->getLang('filelist_plugin_required');
98            print '<a href="http://www.dokuwiki.org/plugin:filelist">The filelist-plugin can be found here.</a>';
99            return;
100        }
101
102        # print out explanation and warning
103        print "<h1>" . $this->getLang('headline') ."</h1>\n";
104        print "<p>" . $this->getLang('description') ."</p>\n";
105        print $this->getLang('detaildesc') ."\n";
106        print $this->_div_warning("start");
107        print $this->getLang('warning_osp') ."\n";
108        print $this->_div_warning("end");
109
110        # determine upload dir from
111        $file_upload = $this->_strip_doubleslashes($conf['savedir'] . '/media/' . $this->getConf('sourcetree') . '/');
112        print "<h2>" . $this->getLang('wizard') . "</h2>\n";
113        print '<div class="settingsbox"><strong>' . $this->getLang('settings') . "</strong><br />";
114        print  $this->getLang('importdir') .": <tt>". $file_upload . "</tt><br />\n";
115        print  $this->getLang('targetns') .": <tt>". $this->getConf('destination_namespace') . "</tt>\n";
116        print "</div>\n";
117
118        $status = $this->_read_status();
119
120        if ($status == "IMPORTED") {
121            $statusline = $this->_read_status("all");
122            print "<p><span class=\"ospok\">OK </span>" . $this->getLang('lastimport') . " " . $statusline . "</p>\n";
123            print $this->_create_reset_form();
124        }
125        if ($status == "DOCSUPLOADED") {
126            print "<p><span class=\"ospok\">OK </span>" . $this->getLang('docsuploaded') . "</p>\n";
127            print "<div class=\"ospnext\">" . $this->getLang('importnow') . "\n";
128            ptln('<form action="'.wl($ID).'" method="post" /> ');
129            ptln(' <input type="hidden" name="do"   value="admin" />');
130            ptln(' <input type="hidden" name="ospcmd"   value="importit" />');
131            ptln(' <input type="hidden" name="page" value="'.$this->getPluginName().'" />');
132            print ' <input type="submit" value="'. $this->getLang('btn_import') . '"> ' . "\n";
133            ptln('</form></div>');
134        }
135        if (is_dir($file_upload) && ($status != "IMPORTED") && ($status != "DOCSUPLOADED") )  {
136            print "<span class=\"ospok\">OK </span>" . $this->getLang('sourcedir_exists') . " <tt> " . $file_upload . " </tt></span>\n";
137            print "<div class=\"ospnext\">" . $this->getLang('docuploadnow') . "\n";
138            ptln('<form action="'.wl($ID).'" method="post" /> ');
139            ptln(' <input type="hidden" name="do"   value="admin" />');
140            ptln(' <input type="hidden" name="ospcmd"   value="docsuploaded" />');
141            ptln(' <input type="hidden" name="page" value="'.$this->getPluginName().'" />');
142            print ' <input type="submit" value="'. $this->getLang('btn_confirm_upload') . '"> ' . "\n";
143            ptln('</form></div>');
144        }
145        if (!is_dir($file_upload) && $status == "START") {
146            ptln("<div class=\"ospnext\">");
147            print $this->getLang('sourcedir_does_not_exist') . "\n ";
148            ptln(' <form action="'.wl($ID).'" method="post" /> ');
149            ptln(' <input type="hidden" name="do" value="admin" />');
150            ptln(' <input type="hidden" name="ospcmd" value="create_upload_dir" />');
151            ptln(' <input type="hidden" name="page" value="'.$this->getPluginName().'" />');
152            print ' <input type="submit" value="' . $this->getLang('btn_create_upload_dir') . '"> ' . "\n";
153            ptln(' </form>');
154            ptln('</div>');
155        }
156
157    }
158
159    /**
160     * Creates creates reset form
161     *
162     * @author   Frank Schiebel <frank@linuxmuster.net>
163     * @param    none
164     * @return   none
165     *
166     **/
167    function _create_reset_form() {
168        global $conf;
169        # determine upload dir from conf
170        $file_upload = $this->_strip_doubleslashes($conf['savedir'] . '/media/' . $this->getConf('sourcetree') . '/');
171        if (@is_dir($file_upload) && @is_writable($file_upload)) {
172            $html =  '<form action="'.wl($ID).'" method="post" /> '."\n";
173            $html .= '  <input type="hidden" name="do" value="admin" />'."\n";
174            $html .= '  <input type="hidden" name="ospcmd" value="delete_upload_dir" />'."\n";
175            $html .= '  <input type="hidden" name="page" value="'.$this->getPluginName().'" />'."\n";
176            $html .= ' <input type="submit" value="' . $this->getLang('btn_delete_upload_dir') . '"> ' . "\n";
177            $html .= '</form>'."\n";
178            $html .=  '<form action="'.wl($ID).'" method="post" /> '."\n";
179            $html .= '  <input type="hidden" name="do" value="admin" />'."\n";
180            $html .= '  <input type="hidden" name="ospcmd" value="importit" />'."\n";
181            $html .= '  <input type="hidden" name="page" value="'.$this->getPluginName().'" />'."\n";
182            $html .= ' <input type="submit" value="' . $this->getLang('btn_reimport') . '"> ' . "\n";
183            $html .= '</form>'."\n";
184        }
185        $html .=  '<form action="'.wl($ID).'" method="post" /> '."\n";
186        $html .= '  <input type="hidden" name="do" value="admin" />'."\n";
187        $html .= '  <input type="hidden" name="ospcmd" value="start_over" />'."\n";
188        $html .= '  <input type="hidden" name="page" value="'.$this->getPluginName().'" />'."\n";
189        $html .= ' <input type="submit" value="' . $this->getLang('btn_start_over') . '"> ' . "\n";
190        $html .= '</form>'."\n";
191        return $html;
192    }
193
194    /**
195     * Creates upload dir according to config
196     *
197     * @author   Frank Schiebel <frank@linuxmuster.net>
198     * @param    none
199     * @return   none
200     *
201     **/
202    function _create_upload_dir() {
203        global $conf;
204        # determine upload dir from conf
205        $file_upload = $this->_strip_doubleslashes($conf['savedir'] . '/media/' . $this->getConf('sourcetree') . '/');
206        mkdir($file_upload);
207        $this->_save_status("UPLOADDIRCREATED");
208    }
209
210    /**
211     * Resets wizard
212     *
213     * @author   Frank Schiebel <frank@linuxmuster.net>
214     * @param    none
215     * @return   none
216     *
217     **/
218    function _reset_wizard() {
219        global $conf;
220        # determine upload dir from conf
221        $file_upload = $this->_strip_doubleslashes($conf['savedir'] . '/media/' . $this->getConf('sourcetree') . '/');
222        if (@is_dir($file_upload) && @is_writable($file_upload)) {
223            $this->_deltree($file_upload);
224        }
225        $this->_save_status("START");
226    }
227
228    /**
229     * Deletes upload dir and all containing docs
230     *
231     * @author   Frank Schiebel <frank@linuxmuster.net>
232     * @param    none
233     * @return   none
234     *
235     **/
236    function _delete_upload_dir() {
237        global $conf;
238        # determine upload dir from conf
239        $file_upload = $this->_strip_doubleslashes($conf['savedir'] . '/media/' . $this->getConf('sourcetree') . '/');
240        if (@is_dir($file_upload) && @is_writable($file_upload)) {
241            $this->_deltree($file_upload);
242        }
243    }
244
245    /**
246     * Import document tree to media dir
247     *
248     * @author   Frank Schiebel <frank@linuxmuster.net>
249     * @param    none
250     * @returns  none
251     *
252     **/
253    function _import_docs() {
254        global $conf;
255        $mediapath = $this->_strip_doubleslashes($conf['savedir'] . '/media/');
256        $pagespath = $this->_strip_doubleslashes($conf['savedir'] . '/pages/');
257        # determine upload dir from conf
258        $file_upload = $this->_strip_doubleslashes($conf['savedir'] . '/media/' . $this->getConf('sourcetree') . '/');
259        $subpath = str_replace(":", "/", $this->getConf('destination_namespace'));
260        $media_dest     = $this->_strip_doubleslashes($mediapath."/".$subpath."/");
261        $pagesdir       = $this->_strip_doubleslashes($pagespath."/".$subpath."/");
262
263        # delete old media and pages dir
264        if (is_dir($media_dest)) {
265            $this->_deltree($media_dest);
266        }
267        if (is_dir($pagesdir)) {
268            $this->_deltree($pagesdir);
269        }
270
271        # create fresh namespacedirs for media an pages
272        io_createNamespace($this->getConf('destination_namespace').":xx");
273        io_createNamespace($this->getConf('destination_namespace').":xx", 'media');
274
275        // copy files recursively
276        $this->_copytree($file_upload, $media_dest);
277        // create startpages
278        $this->create_startpages($media_dest);
279
280        # determine if we are running under openschulportfolio
281        if (file_exists(DOKU_INC . "/lib/tpl/portfolio/ospversion.php")) {
282            $pfstartfile_in  = realpath(dirname(__FILE__))."/start.txt";
283            $pfstartfile_out = $pagespath."portfolio/start.txt";
284            copy($pfstartfile_in, $pfstartfile_out);
285        }
286        $this->_save_status("IMPORTED");
287
288    }
289
290    /**
291     * deletes directory tree recursively
292     *
293     * @author   Frank Schiebel <frank@linuxmuster.net>
294     * @param    string     directory to delete
295     * @returns  boolean    status of rmdir operation
296     *
297     **/
298    function _deltree($dest) {
299        $list = array_diff(scandir($dest), array('.', '..'));
300            foreach ($list as $value) {
301                    $file = $dest.'/'.$value;
302                    if (is_dir($file)) { $this->_deltree($file); } else { unlink($file); }
303            }
304            return rmdir($dest);
305    }
306
307
308    /**
309     * Copy a file, or recursively copy a folder and its contents
310     *
311     * @author      Aidan Lister <aidan@php.net>
312     * @author      Frank Schiebel <frank@linuxmuster.net>
313     * @version     1.0.1
314     * @link        http://aidanlister.com/2004/04/recursively-copying-directories-in-php/
315     * @param       string   $source    Source path
316     * @param       string   $dest      Destination path
317     * @return      bool     Returns TRUE on success, FALSE on failure
318     *
319     **/
320    function _copytree($source, $dest) {
321      global $conf;
322
323      $source = $this->_strip_doubleslashes($source);
324      $dest = $this->_get_clean_filename($dest);
325      // Simple copy for a file
326      if (is_file($source)) {
327        return @copy($source, $dest);
328      }
329      $dest = $this->_get_clean_filename($dest);
330
331      // Make destination directory
332      if (!is_dir($dest)) {
333
334        $mediapath = $this->_strip_doubleslashes($conf['savedir'] . '/media/');
335        $pagespath = $this->_strip_doubleslashes($conf['savedir'] . '/pages/');
336
337
338        $pages_dir = str_replace("$mediapath", "$pagespath", $dest);
339        mkdir($dest);
340        if (!is_dir($pages_dir)) {
341          @mkdir($pages_dir);
342        }
343      }
344
345      // Loop through the folder
346      if (!is_dir($source)) {
347        return false;
348      }
349
350      $dir = dir($source);
351      while (false !== $entry = $dir->read()) {
352        // Skip pointers
353        if ($entry == '.' || $entry == '..') {
354          continue;
355        }
356
357        // Deep copy directories
358        $this->_copytree("$source/$entry", "$dest/$entry");
359      }
360
361      // Clean up
362      $dir->close();
363      return true;
364    }
365
366    /** Gets utf8 encoded wiki filename (experimantal, has to be tested!)
367     *
368    **/
369    function _get_clean_filename($dest) {
370      global $conf;
371
372      $fixpath = $this->_strip_doubleslashes($conf['savedir'] . "/media/");
373      $dest = str_replace("$fixpath", "", $dest);
374      # Fix windows encoding: this is really bad, but i could not figure out how to
375      # change the filename to utf8 from wathever encoding comes in...
376      $dest = urlencode($dest);
377      $dest = str_replace('%2F','/', $dest);
378      $dest = str_replace('%25','%', $dest);
379      $dest = str_replace('%C2','', $dest);
380      $dest = str_replace('%C3','', $dest);
381      $dest = str_replace('%81','ue', $dest);
382      $dest = str_replace('%84','ae', $dest);
383      $dest = str_replace('%94','oe', $dest);
384      $dest = str_replace('%A1','ss', $dest);
385      $dest = str_replace('+','_', $dest);
386      $dest = str_replace('-','_', $dest);
387
388      $dest = str_replace('__','_', $dest);
389      $dest = str_replace("//", "/", $dest);
390      $dest = str_replace("/", ":", $dest);
391      $dest = preg_replace("/:$/", "",  $dest);
392
393      $dest = mediaFN($dest);
394      $dest = $this->_strip_doubleslashes($fixpath . str_replace($conf['mediadir'], '', $dest));
395
396      return $dest;
397    }
398
399    /** recursively creates startpages for imported document structure
400     *
401     **/
402    function create_startpages($dest) {
403      global $conf;
404      $subdirs = array();
405
406      // namespace for filelist
407      $media = $this->_strip_doubleslashes($conf['savedir'] . "/media/");
408      $pages = $this->_strip_doubleslashes($conf['savedir'] . "/pages/");
409
410      // change dest to pages
411      $dest = str_replace($media, $pages, $dest);
412      $startpage = $dest ."start.txt";
413
414      // get filelist namespace
415      $ns_dir = str_replace($pages, "", $dest);
416      $filelist_namespace = str_replace("/",":", $ns_dir);
417      $header_namespace   = preg_replace("|.*/(.*)/$|","$1", $ns_dir);
418
419      // get all subdirs of actual ns
420      $dir = dir($dest);
421      while (false !== $entry = $dir->read()) {
422        // Skip pointers
423        if ($entry == '.' || $entry == '..') {
424          continue;
425        }
426        if (is_dir($dest.$entry)) {
427          $subdirs[] = $entry;
428          // recursively call function
429          $this->create_startpages($dest.$entry."/");
430        }
431      }
432
433      // sort subdirs and create wiki-markup
434      $subdir_out = "";
435      if (count($subdirs) > 0 ) {
436        sort($subdirs);
437        $subdir_out = "===== " . $this->getLang('subnamespaces') ." ===== \n\n";
438        foreach ($subdirs as $subdir) {
439          $subdir_out .= "  * [[.$subdir:start|$subdir]]\n";
440        }
441      }
442
443      // write start.txt
444      $handle = fopen ("$startpage", "w");
445      fwrite($handle, "[[".DOKU_URL."/lib/exe/mediamanager.php?ns=$filelist_namespace|" . $this->getLang('edit_files') . "]] | [[..start|" . $this->getLang('ns_up') . "]] \n\n");
446      fwrite($handle, "====== " . $this->getLang('documents_for') .  $header_namespace . " ======\n\n");
447      fwrite($handle, $this->getLang('docslisted') . "\\\\ ''$filelist_namespace'' \n\n");
448      fwrite($handle, "{{filelist>:$filelist_namespace*&style=table&tableheader=1&tableshowdate=1&tableshowsize=1}}\n\n");
449      fwrite($handle, "$subdir_out\n");
450      fclose($handle);
451    }
452
453    /**
454     * Strip double slashes from path names
455     *
456     * @author   Frank Schiebel <frank@linuxmuster.net>
457     * @param    string     path in
458     * @returns  string     path out
459     *
460     **/
461    function  _strip_doubleslashes($path) {
462        return preg_replace('/\/\//','/', $path);
463    }
464
465    /**
466     * Save status of import procedure to status file
467     * $conf['cachedir'].'/doctree2filelist.status'
468     * creates the file if it does not exist.
469     *
470     * @author   Frank Schiebel <frank@linuxmuster.net>
471     * @param    string     statusstring
472     * @return   true on success
473     *
474     **/
475    function _save_status($status) {
476        global $conf;
477        // build status line
478        $t = time();
479        $statusline = $t."\t".strftime($conf['dformat'],$t)."\t".$_SERVER['REMOTE_ADDR']."\t".$_SERVER['REMOTE_USER']."\t".$status."\n";
480        // write status to file
481        io_saveFile($conf['cachedir'].'/doctree2filelist.status',$statusline, false);
482        return true;
483    }
484
485    /**
486     * reads status of import procedure from status file
487     * $conf['cachedir'].'/doctree2filelist.status'
488     *
489     * @author   Frank Schiebel <frank@linuxmuster.net>
490     * @param    none
491     * @return   string statusstring
492     *
493     **/
494    function _read_status($mode = "statonly") {
495        global $conf;
496        $status = "START";
497        // read status from file
498        $statusfile = $conf['cachedir'].'/doctree2filelist.status';
499        if(@file_exists($statusfile)) {
500            $statusline = file($statusfile);
501            $statusline = $statusline[0];
502            $status = explode("\t", $statusline);
503            if ($mode == "statonly" ) {
504                $status = rtrim($status[4]);
505            } else {
506                $status = $status[1] . " " . $this->getLang('fromuser') . " " . $status[3];
507            }
508        }
509
510        return $status;
511    }
512
513    /**
514     * prints a warning div. Uses the note plugin if available
515     *
516     * @author   Frank Schiebel <frank@linuxmuster.net>
517     * @param    string (start|end)
518     * @return   string html
519     *
520     **/
521    function _div_warning($mode) {
522        if($mode == "start") {
523            if (file_exists(DOKU_PLUGIN . "note/syntax.php")) {
524                return '<div class="notewarning">';
525            } else {
526                return '<div class="docimpwarning">';
527            }
528        } else {
529            return '</div>';
530        }
531    }
532
533
534}
535