MTrackWeb/Wiki.php
[web.mtrack] / MTrackWeb / Wiki.php
1 <?php # vim:ts=2:sw=2:et:
2 /* For licensing and copyright terms, see the file named LICENSE */
3
4
5
6
7 require_once 'MTrack/Attachment.php';
8 require_once 'MTrackWeb.php';
9
10 class MTrackWeb_Wiki extends MTrackWeb
11 {
12     
13     var $conflicted = 0;
14     var $message = false;
15     var $hasHistory = false;
16     
17     function getAuth()
18     {
19         return parent::getAuth();
20     }
21     
22     function get($pi)
23     {
24         $this->pi = empty($pi) ? 'WikiStart' : ($pi . $this->bootLoader->ext);
25  
26         if (!isset($_REQUEST['ajax_body'])) {
27             $this->title = "Browse: " . $this->pi;
28             return;
29         }
30         $this->masterTemplate = 'wiki.html';
31         
32         
33         $this->edit = isset($_REQUEST['edit']) ? (int)$_REQUEST['edit'] : 0;
34         
35  
36         $this->hasHistory = 0;
37         
38         
39         $this->doc = new MTrack_Wiki_Item($this->pi);
40       //  DB_DataObject::debugLevel(1);
41         $this->canEdit = $this->hasPerm('MTrack.Wiki','E');
42         
43         var_dump($this->canEdit);
44         // we might add more perms based on project later..
45         
46          if (!$this->canEdit && $this->edit) {
47             return HTML_FlexyFramework::run('Noperm');
48         }
49         
50         $this->hasHistory = $this->doc->file ? 1: 0;
51         
52         // blank doc.. on edit..
53         if ($this->doc->file === null && $this->edit) {
54             $this->doc = new MTrack_Wiki_Item($this->pi);
55             $this->doc->content = " = {$this->pi} =\n";
56         }
57          
58
59         /* now just render */
60
61         $this->title = $this->pi;
62         if ($this->edit) {
63           $this->title .= " (edit)";
64         }
65         
66         $this->canEdit = $this->edit ? false : $this->canEdit ; // if they are editing remove that permission..
67        
68  
69         if ($this->doc->file) {
70             $this->evt = $this->doc->file->getChangeEvent();
71             if (!strlen($this->evt->changelog)) {
72                 $this->evt->changelog = 'Changed';
73             }
74         }
75         
76         if (!$this->edit  && !$this->hasHistory) {
77             if ($this->canEdit) {
78                 $this->canCreate = 1;
79             } else {
80                 $this->notExist = 1;
81             }
82         }
83         if ($this->edit) {
84             if (isset($_POST['preview'])) {
85                 $this->showPreview = 1;
86                 $this->preview =  MTrackWiki::format_to_html($content);
87             }
88             $this->doc->contentb64= base64_encode($this->doc->content);
89             $this->doc->content = isset($_POST['content']) ? $_POST['content'] : $this->doc->content;
90             $this->comment = isset($_POST['comment']) ? $_POST['comment'] : '';
91
92         }
93    
94         $action = isset($_GET['action']) ? $_GET['action'] : 'view';
95
96         switch ($action) {
97             case 'view':
98                 $this->actionView = 1;
99                 break;
100
101             case 'list':
102                 $this->actionList = 1;
103                 
104                 $htree = array();
105                 $this->build_help_tree($htree, dirname(__FILE__) . '/../defaults/help');
106                 $this->helptree = $htree;
107                 
108   
109                 /* get the page names into a tree format */
110                 $tree = array();
111                 $repo  = false; // 
112                 $root = MTrack_Wiki_Item::getRepoAndRoot($repo);
113                 $suf = MTrackConfig::get('core', 'wikifilenamesuffix'); // fixme!!!!
114                 build_tree($tree, $repo, $root, $suf);
115                 $this->tree = $tree;
116              
117                 break;
118
119             case 'recent':
120                 $this->actionRecent = 1;
121                 $root = MTrack_Wiki_Item::getRepoAndRoot($repo);
122                 $this->recent = $repo->history(null, 100) ;
123                 foreach ($this->recent as $e) {
124                     list($e->page) = $e->files;
125                     if (strlen($root)) {
126                         $e->page = substr($e->page, strlen($root)+1);
127                     }
128                 }
129                  
130                 break;
131
132         }
133         echo '<PRE>';print_r($this);echo '</PRE>';
134     }
135
136     function is_content_conflicted($content)
137     {
138         if (preg_match("/^(<+)\s+(mine|theirs|original)\s*$/m", $content)) {
139             return true;
140         }
141         return false;
142     }
143     function normalize_text($text) {
144         return rtrim($text) . "\n";
145     }
146
147     function post()
148     {
149         $this->get();
150         if (isset($_POST['cancel'])) {
151             header("Location: {$this->baseURL}/Wiki/{$this->pi}");
152             exit;
153         }
154         
155         
156         if (!MTrackCaptcha::check('wiki')) {
157             $this->message = 'CAPTCHA failed, please try again';
158             return;
159         }
160         
161           /* to avoid annoying "you lose because someone else edited" errors,
162            * we compute the diff from the original content we had, and apply
163            * that to the current content of the object */
164
165           $saved = false;
166
167           $orig = base64_decode($_POST['orig']);
168           $content = $_POST['content'];
169
170           /* for consistency, we always want a newline at the end, otherwise
171            * we can end up with some weird output from diff3 */
172           $orig = normalize_text($orig);
173           $content = normalize_text($content);
174           $this->doc->content = normalize_text($this->doc->content);
175           $this->conflicted = is_content_conflicted($content);
176           $tempdir = sys_get_temp_dir();
177
178           if (!$this->conflicted) {
179             $ofile = tempnam($tempdir, "mtrack");
180             $nfile = tempnam($tempdir, "mtrack");
181             $tfile = tempnam($tempdir, "mtrack");
182             $pfile = tempnam($tempdir, "mtrack");
183             
184             require_once 'System.php';
185             $diff3 = System::which('diff3');
186             if (empty($diff3)) {
187               $diff3 = 'diff3';
188             }
189
190             file_put_contents($ofile, $orig);
191             file_put_contents($nfile, $content);
192             file_put_contents($tfile, $this->doc->content);
193
194             if (PHP_OS == 'SunOS') { // seriously does anyone use SunOS anymore???
195                 exec("($diff3 -X $nfile $ofile $tfile ; echo '1,\$p') | ed - $nfile > $pfile",
196                     $output = array(), $retval = 0);
197             } else {
198                 exec("$diff3 -mX --label mine $nfile --label original $ofile --label theirs $tfile > $pfile",
199                     $output = array(), $retval = 0);
200             }
201
202             if ($retval == 0) {
203               /* see if there were merge conflicts */
204               $content = '';
205               $mine = preg_quote($nfile, '/');
206               $theirs = preg_quote($tfile, '/');
207               $orig = preg_quote($ofile, '/');
208               $content = file_get_contents($pfile);
209
210               if (PHP_OS == 'SunOS') {
211                 $content = str_replace($nfile, 'mine', $content);
212                 $content = str_replace($ofile, 'original', $content);
213                 $content = str_replace($tfile, 'theirs', $content);
214               }
215             }
216             unlink($ofile);
217             unlink($nfile);
218             unlink($tfile);
219             unlink($pfile);
220
221             $this->conflicted = is_content_conflicted($content);
222           }
223
224           /* keep the merged version for editing purposes */
225           $_POST['content'] = $content;
226           /* our concept of the the original content is now what
227            * is currently saved */
228           $_POST['orig'] = base64_encode($this->doc->content);
229
230           if ($this->conflicted) {
231             $this->message = "Conflicting edits were detected; please correct them before saving";
232           } else {
233             $this->doc->content = $content;
234             try {
235               $cs = MTrackChangeset::begin("wiki:{$this->pi}", $_POST['comment']);
236               $this->doc->save($cs);
237               if (is_array($_FILES['attachments'])) {
238                 foreach ($_FILES['attachments']['name'] as $fileid => $name) {
239                   $do_attach = false;
240                   switch ($_FILES['attachments']['error'][$fileid]) {
241                     case UPLOAD_ERR_OK:
242                       $do_attach = true;
243                       break;
244                     case UPLOAD_ERR_NO_FILE:
245                       break;
246                     case UPLOAD_ERR_INI_SIZE:
247                     case UPLOAD_ERR_FORM_SIZE:
248                       $this->message = "Attachment(s) exceed the upload file size limit";
249                       break;
250                     case UPLOAD_ERR_PARTIAL:
251                     case UPLOAD_ERR_CANT_WRITE:
252                       $this->message = "Attachment file upload failed";
253                       break;
254                     case UPLOAD_ERR_NO_TMP_DIR:
255                       $this->message = "Server configuration prevents upload due to missing temporary dir";
256                       break;
257                     case UPLOAD_ERR_EXTENSION:
258                       $this->message = "An extension prevented an upload from running";
259                   }
260                   if ($this->message !== null) {
261                     throw new Exception($this->message);
262                   }
263                   if ($do_attach) {
264                     MTrackAttachment::add("wiki:{$this->pi}",
265                       $_FILES['attachments']['tmp_name'][$fileid],
266                       $_FILES['attachments']['name'][$fileid],
267                       $cs);
268                   }
269                 }
270               }
271               MTrackAttachment::process_delete("wiki:{$this->pi}", $cs);
272               $cs->commit();
273               MTrack_Wiki_Item::commitNow();
274               $saved = true;
275             } catch (Exception $e) {
276               $this->message = $e->getMessage();
277             }
278           }
279
280           if ($saved) {
281                 /* we're good; go back to view mode */
282                 header("Location: {$this->baseURL}/Wiki/{$this->pi}");
283                 exit;
284           }
285         }
286         
287         function build_help_tree(&$tree, $dir) {
288             foreach (scandir($dir) as $ent) {
289               if ($ent[0] == '.') {
290                 continue;
291               }
292               $full = $dir . DIRECTORY_SEPARATOR . $ent;
293               if (is_dir($full)) {
294                 $kid = array();
295                 $this->build_help_tree($kid, $full);
296                 $tree[$ent] = $kid;
297               } else {
298                 $tree[$ent] = array();
299               }
300             }
301         }
302         function build_tree(&$tree, $repo, $dir, $suf)
303         {
304             $items = $repo->readdir($dir);
305             foreach ($items as $file) {
306               $label = basename($file->name);
307               if ($file->is_dir) {
308                 $kid = array();
309                 $this->build_tree($kid, $repo, $file->name, $suf);
310                 $tree[$label] = $kid;
311               } else {
312                 if ($suf && substr($label, -strlen($suf)) == $suf) {
313                   $label = substr($label, 0, strlen($label) - strlen($suf));
314                 }
315                 $tree[$label] = array();
316               }
317             }
318         }   
319         function emit_tree($root,  $phppage,$parent='')
320         {
321  
322             if (strlen($parent)) {
323               echo "<ul>\n";
324             } else {
325               echo "<ul class='wikitree'>\n";
326             }
327             $knames = array_keys($root);
328             usort($knames, 'strnatcasecmp');
329             foreach ($knames as $key) {
330               $kids = $root[$key];
331               $n = htmlentities($key, ENT_QUOTES, 'utf-8');
332               echo "<li>";
333               if (count($kids)) {
334                 echo $n;
335                 emit_tree($kids,  $phppage,"$parent$key/");
336               } else {
337                 echo "<a href=\"{$this->baseURL}/$phppage/$parent$n\">$n</a>";
338               }
339               echo "</li>\n";
340             }
341             echo "</ul>\n";
342         }
343
344         
345         function renderDeleteList() {
346             //return MTrackAttachment::renderDeleteList("wiki:{$this->pi}");
347         }
348         function renderList() {
349             //return MTrackAttachment::renderList("wiki:{$this->pi}");
350         }
351               
352
353         function captcha(){
354             return MTrackCaptcha::emit('wiki');
355         }
356     }