import
[web.mtrack] / web / wiki.php
1 <?php # vim:ts=2:sw=2:et:
2 /* For licensing and copyright terms, see the file named LICENSE */
3
4 include '../inc/common.php';
5 #error_reporting(E_ALL | E_NOTICE);
6
7 $pi = mtrack_get_pathinfo();
8 if (empty($pi)) {
9   $pi = "WikiStart";
10 }
11
12 $edit = isset($_REQUEST['edit']) ? (int)$_REQUEST['edit'] : null;
13 $message = null;
14 $conflicted = false;
15
16 function is_content_conflicted($content)
17 {
18   if (preg_match("/^(<+)\s+(mine|theirs|original)\s*$/m", $content)) {
19     return true;
20   }
21   return false;
22 }
23 function normalize_text($text) {
24   return rtrim($text) . "\n";
25 }
26
27
28 if ($pi !== null) {
29   $doc = MTrackWikiItem::loadByPageName($pi);
30   if ($doc) {
31     MTrackACL::requireAnyRights("wiki:$doc->pagename",
32       $edit ? 'modify' : 'read');
33   } else {
34     MTrackACL::requireAnyRights("wiki:$pi",
35       $edit ? 'modify' : 'read');
36   }
37   if ($doc === null && $edit) {
38     $doc = new MTrackWikiItem($pi);
39     $doc->content = " = $pi =\n";
40   }
41
42   if ($_SERVER['REQUEST_METHOD'] == 'POST') {
43
44     if (isset($_POST['cancel'])) {
45       header("Location: ${ABSWEB}wiki.php/$pi");
46       exit;
47     }
48     if (!MTrackCaptcha::check('wiki')) {
49       $message = 'CAPTCHA failed, please try again';
50     } else if (isset($_POST['save'])) {
51       /* to avoid annoying "you lose because someone else edited" errors,
52        * we compute the diff from the original content we had, and apply
53        * that to the current content of the object */
54
55       $saved = false;
56
57       $orig = base64_decode($_POST['orig']);
58       $content = $_POST['content'];
59
60       /* for consistency, we always want a newline at the end, otherwise
61        * we can end up with some weird output from diff3 */
62       $orig = normalize_text($orig);
63       $content = normalize_text($content);
64       $doc->content = normalize_text($doc->content);
65       $conflicted = is_content_conflicted($content);
66       $tempdir = sys_get_temp_dir();
67
68       if (!$conflicted) {
69         $ofile = tempnam($tempdir, "mtrack");
70         $nfile = tempnam($tempdir, "mtrack");
71         $tfile = tempnam($tempdir, "mtrack");
72         $pfile = tempnam($tempdir, "mtrack");
73         $diff3 = MTrackConfig::get('tools', 'diff3');
74         if (empty($diff3)) {
75           $diff3 = 'diff3';
76         }
77
78         file_put_contents($ofile, $orig);
79         file_put_contents($nfile, $content);
80         file_put_contents($tfile, $doc->content);
81
82         if (PHP_OS == 'SunOS') {
83           exec("($diff3 -X $nfile $ofile $tfile ; echo '1,\$p') | ed - $nfile > $pfile",
84             $output = array(), $retval = 0);
85         } else {
86           exec("$diff3 -mX --label mine $nfile --label original $ofile --label theirs $tfile > $pfile",
87             $output = array(), $retval = 0);
88         }
89
90         if ($retval == 0) {
91           /* see if there were merge conflicts */
92           $content = '';
93           $mine = preg_quote($nfile, '/');
94           $theirs = preg_quote($tfile, '/');
95           $orig = preg_quote($ofile, '/');
96           $content = file_get_contents($pfile);
97
98           if (PHP_OS == 'SunOS') {
99             $content = str_replace($nfile, 'mine', $content);
100             $content = str_replace($ofile, 'original', $content);
101             $content = str_replace($tfile, 'theirs', $content);
102           }
103         }
104         unlink($ofile);
105         unlink($nfile);
106         unlink($tfile);
107         unlink($pfile);
108
109         $conflicted = is_content_conflicted($content);
110       }
111
112       /* keep the merged version for editing purposes */
113       $_POST['content'] = $content;
114       /* our concept of the the original content is now what
115        * is currently saved */
116       $_POST['orig'] = base64_encode($doc->content);
117
118       if ($conflicted) {
119         $message = "Conflicting edits were detected; please correct them before saving";
120       } else {
121         $doc->content = $content;
122         try {
123           $cs = MTrackChangeset::begin("wiki:$pi", $_POST['comment']);
124           $doc->save($cs);
125           if (is_array($_FILES['attachments'])) {
126             foreach ($_FILES['attachments']['name'] as $fileid => $name) {
127               $do_attach = false;
128               switch ($_FILES['attachments']['error'][$fileid]) {
129                 case UPLOAD_ERR_OK:
130                   $do_attach = true;
131                   break;
132                 case UPLOAD_ERR_NO_FILE:
133                   break;
134                 case UPLOAD_ERR_INI_SIZE:
135                 case UPLOAD_ERR_FORM_SIZE:
136                   $message = "Attachment(s) exceed the upload file size limit";
137                   break;
138                 case UPLOAD_ERR_PARTIAL:
139                 case UPLOAD_ERR_CANT_WRITE:
140                   $message = "Attachment file upload failed";
141                   break;
142                 case UPLOAD_ERR_NO_TMP_DIR:
143                   $message = "Server configuration prevents upload due to missing temporary dir";
144                   break;
145                 case UPLOAD_ERR_EXTENSION:
146                   $message = "An extension prevented an upload from running";
147               }
148               if ($message !== null) {
149                 throw new Exception($message);
150               }
151               if ($do_attach) {
152                 MTrackAttachment::add("wiki:$pi",
153                   $_FILES['attachments']['tmp_name'][$fileid],
154                   $_FILES['attachments']['name'][$fileid],
155                   $cs);
156               }
157             }
158           }
159           MTrackAttachment::process_delete("wiki:$pi", $cs);
160           $cs->commit();
161           MTrackWikiItem::commitNow();
162           $saved = true;
163         } catch (Exception $e) {
164           $message = $e->getMessage();
165         }
166       }
167
168       if ($saved) {
169         /* we're good; go back to view mode */
170         header("Location: ${ABSWEB}wiki.php/$pi");
171         exit;
172       }
173     }
174   }
175 }
176
177 /* now just render */
178
179 $title = $pi;
180 if ($edit) {
181   $title .= " (edit)";
182 }
183 mtrack_head($title);
184 $ppi = htmlentities($pi, ENT_QUOTES, 'utf-8');
185 $editurl = $ABSWEB . "wiki.php/$pi";
186
187 $nav = array();
188
189 if (!$edit && MTrackACL::hasAnyRights("wiki:$pi", 'modify')) {
190   $nav["$editurl?edit=1"] = 'Edit this Page';
191 }
192
193 if ($doc) {
194   $nav["/log.php/default/wiki/$doc->filename"] = "Page History";
195 }
196
197 $nav["/wiki.php?action=list"] = "Help &amp; Title Index";
198 $nav["/wiki.php?action=recent"] = "Recent Changes";
199
200 if ($doc && $doc->file) {
201   $evt = $doc->file->getChangeEvent();
202   $reason = $evt->changelog;
203   if (!strlen($evt->changelog)) {
204     $reason = 'Changed';
205   }
206   $reason = htmlentities($reason, ENT_QUOTES, 'utf-8');
207   echo "<div id='wikilastchange'>",
208     mtrack_username($evt->changeby, array('no_name' => true,
209     'class' => 'wikilastchange')),
210     "$reason by ",
211     mtrack_username($evt->changeby, array('no_image' => true)), " ",
212     mtrack_date($evt->ctime),
213     "</div>\n";
214 }
215
216 echo mtrack_nav("wikinav", $nav);
217
218 if (strlen($message)) {
219   echo "<br><div class='ui-state-error ui-corner-all'>" .
220     "<span class='ui-icon ui-icon-alert'></span>\n" .
221     htmlentities($message, ENT_QUOTES, 'utf-8') .
222     "</div>";
223 }
224
225 if (count($_GET) == 0 && ($doc === null || strlen($doc->content) == 0)) {
226   if (MTrackACL::hasAnyRights("wiki:$pi", 'create')) {
227     echo "Wiki page $ppi doesn't exist, would you like to create it?<br>";
228
229     echo <<<HTML
230 <form name="launchwikiedit" method="GET" action="$editurl">
231 <input type="hidden" name="edit" value="1"/>
232 <button type="submit">Edit this page</button>
233 </form>
234 HTML;
235   } else {
236     echo "Wiki page $ppi doesn't exist.<br>";
237   }
238
239 } elseif ($edit) {
240   echo "<h1>Editing $ppi</h1>";
241   echo "<a href=\"{$ABSWEB}/help.php/WikiFormatting\" target=\"_blank\">Wiki Formatting</a> (opens in a new window)<br>\n";
242
243   $orig_content = isset($_POST['orig']) ? $_POST['orig']
244                     : base64_encode($doc->content);
245   $content = isset($_POST['content']) ? $_POST['content'] : $doc->content;
246   $comment = isset($_POST['comment']) ? $_POST['comment'] : '';
247   $comment = htmlentities($comment, ENT_QUOTES, 'utf-8');
248
249   if (isset($_POST['preview'])) {
250     echo "<div class='wikipreview'>" .
251       MTrackWiki::format_to_html($content) . "</div>";
252   }
253
254   echo <<<HTML
255 <form name="wikiedit" method="POST" action="$editurl" enctype='multipart/form-data'>
256 <input type="hidden" name="edit" value="1"/>
257 <input type="hidden" name="orig" value="$orig_content"/>
258 HTML;
259
260     if ($conflicted) {
261       echo "<input type='hidden' name='conflicted' value='1'/>";
262     }
263
264     echo <<<HTML
265 <textarea name="content" class="wiki"
266   rows="36" cols="78" style="width:100%;">$content</textarea>
267 <fieldset>
268   <legend>Attachments</legend>
269 HTML;
270     echo MTrackAttachment::renderDeleteList("wiki:$pi");
271     echo <<<HTML
272   <label for='attachments[]'>Select file(s) to be attached</label>
273   <input type='file' class='multi' name='attachments[]'>
274 </fieldset>
275 <fieldset id="changeinfo">
276   <legend>Change Information</legend>
277   <div class="field"><label>Comment about the change:<br/>
278     <input type="text" name="comment" size="60" value="$comment"/>
279   </label></div>
280 HTML;
281     echo MTrackCaptcha::emit('wiki');
282     echo <<<HTML
283   <div class="buttons">
284     <button type="submit" name="preview">Preview</button>
285     <button type="submit" name="save">Save changes</button>
286     <button type="submit" name="cancel">Cancel</button>
287   </div>
288 </form>
289
290 HTML;
291
292 } else {
293   $action = isset($_GET['action']) ? $_GET['action'] : 'view';
294
295   switch ($action) {
296     case 'view':
297       echo MTrackWiki::format_to_html($doc->content);
298       echo MTrackAttachment::renderList("wiki:$pi");
299       if (MTrackACL::hasAnyRights("wiki:$doc->pagename", 'modify')) {
300         echo <<<HTML
301 <form name="launchwikiedit" method="GET" action="$editurl">
302 <input type="hidden" name="edit" value="1"/>
303 <button type="submit">Edit this page</button>
304 </form>
305 HTML;
306       }
307       break;
308
309     case 'list':
310       echo "<h1>Help topics by Title</h1>\n";
311       $htree = array();
312
313       function build_help_tree(&$tree, $dir) {
314         foreach (scandir($dir) as $ent) {
315           if ($ent[0] == '.') {
316             continue;
317           }
318           $full = $dir . DIRECTORY_SEPARATOR . $ent;
319           if (is_dir($full)) {
320             $kid = array();
321             build_help_tree($kid, $full);
322             $tree[$ent] = $kid;
323           } else {
324             $tree[$ent] = array();
325           }
326         }
327       }
328       function emit_tree($root, $parent, $phppage)
329       {
330         global $ABSWEB;
331
332         if (strlen($parent)) {
333           echo "<ul>\n";
334         } else {
335           echo "<ul class='wikitree'>\n";
336         }
337         $knames = array_keys($root);
338         usort($knames, 'strnatcasecmp');
339         foreach ($knames as $key) {
340           $kids = $root[$key];
341           $n = htmlentities($key, ENT_QUOTES, 'utf-8');
342           echo "<li>";
343           if (count($kids)) {
344             echo $n;
345             emit_tree($kids, "$parent$key/", $phppage);
346           } else {
347             echo "<a href=\"${ABSWEB}$phppage/$parent$n\">$n</a>";
348           }
349           echo "</li>\n";
350         }
351         echo "</ul>\n";
352       }
353
354       build_help_tree($htree, dirname(__FILE__) . '/../defaults/help');
355       emit_tree($htree, '', 'help.php');
356
357       echo "<h1>Wiki pages by Title</h1>\n";
358       /* get the page names into a tree format */
359       $tree = array();
360       $root = MTrackWikiItem::getRepoAndRoot($repo);
361       $suf = MTrackConfig::get('core', 'wikifilenamesuffix');
362
363       function build_tree(&$tree, $repo, $dir, $suf) {
364         $items = $repo->readdir($dir);
365         foreach ($items as $file) {
366           $label = basename($file->name);
367           if ($file->is_dir) {
368             $kid = array();
369             build_tree($kid, $repo, $file->name, $suf);
370             $tree[$label] = $kid;
371           } else {
372             if ($suf && substr($label, -strlen($suf)) == $suf) {
373               $label = substr($label, 0, strlen($label) - strlen($suf));
374             }
375             $tree[$label] = array();
376           }
377         }
378       }
379
380       build_tree($tree, $repo, $root, $suf);
381
382       emit_tree($tree, '', 'wiki.php');
383
384       echo <<<HTML
385 <script type='text/javascript'>
386 $(document).ready(function(){
387   $('ul.wikitree').treeview({
388     collapsed: true,
389     persist: "location"
390   });
391 });
392 </script>
393 HTML;
394
395       break;
396
397     case 'recent':
398       echo <<<HTML
399 <h1>Recently Edited Wiki Pages</h1>
400 <table class="history">
401   <tr>
402     <th>Page</th>
403     <th>Date</th>
404     <th>Who</th>
405     <th>Reason</th>
406   </tr>
407 HTML;
408       $root = MTrackWikiItem::getRepoAndRoot($repo);
409       foreach ($repo->history(null, 100) as $e) {
410         $d = mtrack_date($e->ctime);
411         list($page) = $e->files;
412         if (strlen($root)) {
413           $page = substr($page, strlen($root)+1);
414         }
415         $author = mtrack_username($e->changeby);
416         $reason = htmlentities($e->changelog, ENT_QUOTES, 'utf-8');
417
418         echo "<tr><td><a href=\"${ABSWEB}wiki.php/$page\">$page</a></td><td>$d</td><td>$author</td><td>$reason</td></tr>\n";
419       }
420
421       echo <<<HTML
422 </table>
423 HTML;
424
425       break;
426
427   }
428 }
429 mtrack_foot();