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