import
[web.mtrack] / web / browse.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
6 $USE_AJAX = false;
7
8 MTrackACL::requireAllRights('Browser', 'read');
9
10 $pi = mtrack_get_pathinfo(true);
11 $crumbs = MTrackSCM::makeBreadcrumbs($pi);
12 if (!strlen($pi) || $pi == '/') {
13   $pi = '/';
14 }
15 if (count($crumbs) > 2) {
16   $repo = MTrackSCM::factory($pi);
17 } else {
18   $repo = null;
19 }
20
21 if (!isset($_GET['_'])) {
22   $AJAX = false;
23 } else {
24   $AJAX = true;
25 }
26
27 function one_line_cl($changelog)
28 {
29   list($one) = explode("\n", $changelog);
30   return rtrim($one, " \r\n");
31 }
32
33 function get_browse_data($repo, $pi, $object, $ident)
34 {
35   global $ABSWEB;
36
37   $data = new stdclass;
38   $data->dirs = array();
39   $data->files = array();
40   $data->jumps = array();
41
42   if (!$repo) {
43     return $data;
44   }
45   $branches = $repo->getBranches();
46   $tags = $repo->getTags();
47   if (count($branches) + count($tags)) {
48     $jumps = array("" => "- Select Branch / Tag - ");
49     if (is_array($branches)) {
50       foreach ($branches as $name => $notcare) {
51         $jumps["branch:$name"] = "Branch: $name";
52       }
53     }
54     if (is_array($tags)) {
55       foreach ($tags as $name => $notcare) {
56         $jumps["tag:$name"] = "Tag: $name";
57       }
58     }
59     $data->jumps = $jumps;
60   }
61   $files = array();
62   $dirs = array();
63
64   if ($repo) {
65     try {
66       $ents = $repo->readdir($pi, $object, $ident);
67     } catch (Exception $e) {
68       // Typically a freshly created repo
69       $ents = array();
70       $data->err = $e->getMessage();
71     }
72     foreach ($ents as $file) {
73       $basename = basename($file->name);
74       if ($file->is_dir) {
75         $dirs[$basename] = $file;
76       } else {
77         $files[$basename] = $file;
78       }
79     }
80   }
81   uksort($files, 'strnatcmp');
82   uksort($dirs, 'strnatcmp');
83
84   $data->files = array();
85   $data->dirs = array();
86
87   $urlbase = $ABSWEB . 'browse.php';
88   $pathbase = '/' . $repo->getBrowseRootName();
89   $urlbase .= $pathbase;
90
91   foreach ($dirs as $basename => $file) {
92     $ent = $file->getChangeEvent();
93     $url = $urlbase . '/' . $file->name;
94     $d = new stdclass;
95     $d->url = $url;
96     $d->basename = $basename;
97     $d->rev = $ent->rev;
98     $d->ctime = $ent->ctime;
99     $d->changeby = $ent->changeby;
100     $d->changelog = one_line_cl($ent->changelog);
101
102     $data->dirs[] = $d;
103   }
104   foreach ($files as $basename => $file) {
105     $ent = $file->getChangeEvent();
106     $url = $ABSWEB . 'file.php' . $pathbase .
107             '/' . $file->name . '?rev=' . $ent->rev;
108     $d = new stdclass;
109     $d->url = $url;
110     $d->basename = $basename;
111     $d->rev = $ent->rev;
112     $d->ctime = $ent->ctime;
113     $d->changeby = $ent->changeby;
114     $d->changelog = one_line_cl($ent->changelog);
115
116     $data->files[] = $d;
117   }
118
119   return $data;
120 }
121
122 if (isset($_GET['jump']) && strlen($_GET['jump'])) {
123   list($object, $ident) = explode(':', $_GET['jump'], 2);
124 } else {
125   $object = null;
126   $ident = null;
127 }
128
129 if ($USE_AJAX && !$AJAX) {
130   mtrack_head("Browse $pi");
131
132   // Since big dirs can take a while to gather the browse data,
133   // We want to show *something* to the user while we wait for
134   // the data to come in
135   $g = $_GET;
136   $g['_'] = '_';
137   $url = $_SERVER['REQUEST_URI'] . '?' . http_build_query($g);
138   echo <<<HTML
139 <div id='browsediv'>
140   <p>Loading browse data, please wait</p>
141 </div>
142 <script>
143 \$(document).ready(function () {
144   \$('#browsediv').load('$url');
145 });
146 </script>
147 HTML;
148   mtrack_foot();
149 } else {
150   if (!$USE_AJAX) {
151     mtrack_head("Browse $pi");
152   }
153
154 $bdata = mtrack_cache('get_browse_data',
155   array($repo, $pi, $object, $ident));
156
157 if (isset($bdata->err) && strlen($pi) > 1) {
158   throw new Exception($bdata->err);
159 }
160
161 /* Render a bread-crumb enabled location indicator */
162 echo "<div class='browselocation'>Location: ";
163 $location = null;
164 foreach ($crumbs as $path) {
165   if (!strlen($path)) {
166     $path = '[root]';
167   } else {
168     $location .= '/' . urlencode($path);
169   }
170   $path = htmlentities($path, ENT_QUOTES, 'utf-8');
171   echo "<a href='{$ABSWEB}browse.php$location'>$path</a> / ";
172 }
173
174 if (count($bdata->jumps)) {
175   echo "<form>";
176   echo mtrack_select_box("jump", $bdata->jumps,
177         isset($_GET['jump']) ? $_GET['jump'] : null);
178   echo "<button type='submit'>Choose</button></form>\n";
179 }
180
181 echo "</div>";
182
183 $me = mtrack_canon_username(MTrackAuth::whoami());
184 if (MTrackACL::hasAllRights('Browser', 'create')) {
185   /* some users may have rights to create repos that belong to projects.
186     * Determine that list of projects here, because we need it for both
187     * the fork and new repo cases */
188   $owners = array("user:$me" => $me);
189
190   foreach (MTrackDB::q(
191       'select projid, shortname, name from projects order by ordinal')
192       as $row)
193   {
194     if (MTrackACL::hasAllRights("project:$row[0]", 'modify')) {
195       $owners["project:$row[1]"] = $row[1];
196     }
197   }
198   if (count($owners) > 1) {
199     $owners = mtrack_select_box('repo:parent', $owners, null, true);
200   } else {
201     $owners = '';
202   }
203 }
204
205 if ($repo) {
206   MTrackACL::requireAllRights("repo:$repo->repoid", 'read');
207
208   $description = MTrackWiki::format_to_html($repo->description);
209   $url = $repo->getCheckoutCommand();
210
211   echo "<div class='repodesc'>$description</div>";
212   if (strlen($url)) {
213     echo "<div class='checkout'>\n";
214     echo "Use the following command to obtain a working copy:<br>";
215     echo "<pre>\$ $url</pre>";
216     echo "</div>\n";
217   }
218
219
220   if ($repo->canFork() && MTrackACL::hasAllRights('Browser', 'fork')
221       && MTrackConfig::get('repos', 'allow_user_repo_creation')) {
222     $forkname = "$me/$repo->shortname";
223     if ($forkname == $repo->getBrowseRootName()) {
224       /* if this is mine already, make a "more unique" name for my new fork */
225       $forkname = $repo->shortname . '2';
226     } else {
227       $forkname = $repo->shortname;
228     }
229     $forkname = htmlentities($forkname, ENT_QUOTES, 'utf-8');
230     echo <<<FORK
231 <div id='forkdialog' style='display:none'
232   title='Really create a fork?'>
233 <form id='forkform' action='${ABSWEB}admin/forkrepo.php' method='post'>
234   <input type='hidden' name='source' value='$repo->repoid'>
235   <p>
236     A fork is your own copy of a repo that is stored and maintained
237     on the server.
238   </p>
239   <p>
240     If all you want to do is obtain a working copy so that you can
241     collaborate on this repo, you should not create a fork.
242   </p>
243   <p>
244     You may want to fork if you want the server to keep your work backed up,
245     or to collaborate with others on work that you want to share
246     with this repo later on.
247   </p>
248   <p>
249     Choose a name for your fork:
250     $owners <input type='text' name='name' value='$forkname'>
251   </p>
252 </form>
253 </div>
254 <button id='forkbtn' type='button'>Fork</button>
255 <script>
256 \$(document).ready(function() {
257   \$('#forkdialog').dialog({
258     autoOpen: false,
259     bgiframe: true,
260     resizable: false,
261     width: 600,
262     modal: true,
263     buttons: {
264       'No': function() {
265         $(this).dialog('close');
266       },
267       'Fork': function() {
268         $('#forkform').submit();
269       }
270     }
271   });
272   \$('#forkbtn').click(function () {
273     \$('#forkdialog').dialog('open');
274     return false;
275   });
276 });
277 </script>
278 FORK
279     ;
280   }
281   $mine = "user:$me";
282   if ($repo->parent &&
283       MTrackACL::hasAllRights("repo:$repo->repoid", "delete")) {
284     echo <<<FORK
285       <div id='deletedialog' style='display:none'
286       title='Really delete this repo?'>
287       <form id='deleteform' action='${ABSWEB}admin/deleterepo.php'
288         method='post'>
289       <input type='hidden' name='repoid' value='$repo->repoid'>
290       <p>Are you sure you want to delete this repo?</p>
291       <p><b>You cannot undo this action; any data will be permanently
292         deleted</b></p>
293       </form>
294       </div>
295       <button id='deletebtn' type='button'>Delete</button>
296 <script>
297 \$(document).ready(function() {
298   \$('#deletedialog').dialog({
299     autoOpen: false,
300     bgiframe: true,
301     resizable: false,
302     modal: true,
303     buttons: {
304       'No': function() {
305         $(this).dialog('close');
306       },
307       'Delete': function() {
308         $('#deleteform').submit();
309       }
310     }
311   });
312   \$('#deletebtn').click(function () {
313     \$('#deletedialog').dialog('open');
314     return false;
315   });
316 });
317 </script>
318 FORK
319 ;
320   }
321   if (MTrackACL::hasAllRights("repo:$repo->repoid", "modify")) {
322     echo <<<EDIT
323 <a class='button' href='{$ABSWEB}admin/repo.php/$repo->repoid'>Edit</a>
324 EDIT
325     ;
326   }
327   MTrackWatch::renderWatchUI('repo', $repo->repoid);
328
329   echo "<br>\n<a href='{$ABSWEB}log.php/{$repo->getBrowseRootName()}/$pi'>Show History</a><br>\n";
330 }
331
332 if (!$repo && MTrackACL::hasAllRights('Browser', 'fork')
333     && MTrackConfig::get('repos', 'allow_user_repo_creation')) {
334 $repotypes = array();
335 foreach (MTrackRepo::getAvailableSCMs() as $t => $r) {
336   $d = $r->getSCMMetaData();
337   $repotypes[$t] = $d['name'];
338 }
339 $repotypes = mtrack_select_box("repo:type", $repotypes, null, true);
340 echo <<<NEWREPO
341 <div id='newdialog' style='display:none'
342   title='Create a new repo?'>
343 <form id='newrepoform' action='${ABSWEB}admin/repo.php/new' method='post'>
344 <p>
345   Choose a name for your repo:
346   $owners <input type='text' name='repo:name' value='myrepo'>
347 </p>
348 <p>
349   Choose a repository type: $repotypes
350 </p>
351 <p>
352   Description:<br>
353   <em>You may use <a href='{$ABSWEB}help.php/WikiFormatting' target='_blank'>WikiFormatting</a></em><br>
354   <textarea name='repo:description' class='wiki shortwiki' rows='5' cols='78'></textarea>
355 </form>
356 </div>
357 <button id='newrepobtn' type='button'>New</button>
358 <script>
359 \$(document).ready(function() {
360   \$('#newdialog').dialog({
361     autoOpen: false,
362     bgiframe: true,
363     resizable: false,
364     width: 600,
365     modal: true,
366     buttons: {
367       'Cancel': function() {
368         $(this).dialog('close');
369       },
370       'Create': function() {
371         $('#newrepoform').submit();
372       }
373     }
374   });
375   \$('#newrepobtn').click(function () {
376     \$('#newdialog').dialog('open');
377     return false;
378   });
379 });
380 </script>
381 NEWREPO
382 ;
383 }
384
385 echo "<br>\n";
386
387 ?>
388 <table class='listing' id='dirlist'>
389   <thead>
390     <tr>
391 <?php
392 if (!$repo) {
393 ?>
394       <th class='name' width='1%'>Name</th>
395       <th class='desc'>Description</th>
396 <?php
397 } else {
398 ?>
399       <th class='name' width='1%'>Name</th>
400       <th class='rev' width='1%'>Revision</th>
401       <th class='age' width='1%'>Age</th>
402       <th class='change'>Last Change</th>
403 <?php
404 }
405 ?>
406     </tr>
407   </thead>
408   <tbody>
409 <?php
410 $even = 1;
411
412 if (count($crumbs) > 1) {
413   $class = $even++ % 2 ? 'even' : 'odd';
414   $url = $ABSWEB . 'browse.php' . dirname(mtrack_get_pathinfo(true));
415   if (isset($_GET['jump'])) {
416     $url .= '?jump=' . urlencode($_GET['jump']);
417   }
418   $url = htmlentities($url, ENT_QUOTES, 'utf-8');
419
420   echo "<tr class='$class'>\n";
421   echo "<td class='name'><a class='parent' href='$url'>.. [up]</a></td>";
422   if ($repo) {
423     echo "<td class='rev'></td>\n";
424     echo "<td class='age'></td>\n";
425     echo "<td class='change'></td>\n";
426   } else {
427     echo "<td class='desc'></td>\n";
428   }
429   echo "</tr>\n";
430 }
431
432 foreach ($bdata->dirs as $d) {
433   $class = $even++ % 2 ? 'even' : 'odd';
434   $url = $d->url;
435   if (isset($_GET['jump'])) {
436     $url .= '?jump=' . urlencode($_GET['jump']);
437   }
438   $url = htmlentities($url, ENT_QUOTES, 'utf-8');
439   echo "<tr class='$class'>\n";
440   echo "<td class='name'><a class='dir' href='$url'>$d->basename</a></td>";
441   echo "<td class='rev'>" . mtrack_changeset($d->rev, $repo) . "</td>\n";
442   echo "<td class='age'>" . mtrack_date($d->ctime) . "</td>\n";
443   echo "<td class='change'>" .
444     mtrack_username($d->changeby, array('size' => 16)) . ": " .
445     MTrackWiki::format_to_oneliner($d->changelog) . "</td>\n";
446   echo "</tr>\n";
447 }
448
449 foreach ($bdata->files as $d) {
450   $class = $even++ % 2 ? 'even' : 'odd';
451   $url = $d->url;
452   if (isset($_GET['jump'])) {
453     $url .= '&jump=' . urlencode($_GET['jump']);
454   }
455   $url = htmlentities($url, ENT_QUOTES, 'utf-8');
456   echo "<tr class='$class'>\n";
457   echo "<td class='name'><a class='file' href='$url'>$d->basename</a></td>";
458   echo "<td class='rev'>" . mtrack_changeset($d->rev, $repo) . "</td>\n";
459   echo "<td class='age'>" . mtrack_date($d->ctime) . "</td>\n";
460   echo "<td class='change'>" .
461     mtrack_username($d->changeby, array('size' => 16)) . ": " .
462     MTrackWiki::format_to_oneliner($d->changelog) . "</td>\n";
463   echo "</tr>\n";
464 }
465
466 if (!$repo) {
467   $mine = 'user:' . mtrack_canon_username(MTrackAuth::whoami());
468   $params = array();
469   if (count($crumbs) == 2 && $crumbs[1] != 'default') {
470     /* looking for a particular subset */
471     $where = "parent like('%:' || ?)";
472     $params[] = $crumbs[1];
473   } else if (count($crumbs) == 2 && $crumbs[1] == 'default') {
474     /* looking at system items */
475     $where = "parent = ''";
476   } else {
477     /* looking for top level items */
478     $where = "1 = 1";
479   }
480   /* have my own repos bubble up */
481   $params[] = $mine;
482   $sql = <<<SQL
483 select repoid, parent, shortname, description
484 from repos
485 where $where
486 order by
487   case when parent = ? then 0 else 1 end,
488   shortname
489 SQL
490   ;
491   $q = MTrackDB::get()->prepare($sql);
492   $q->execute($params);
493
494   foreach ($q->fetchAll(PDO::FETCH_OBJ) as $rep) {
495     if (!MTrackACL::hasAnyRights("repo:$rep->repoid", 'read')) {
496       continue;
497     }
498
499     $class = $even++ % 2 ? 'even' : 'odd';
500     $url = $ABSWEB . 'browse.php/';
501     $label = MTrackRepo::makeDisplayName($rep);
502
503     $url .= $label;
504     echo "<tr class='$class'>\n";
505     echo "<td class='name'><a class='dir' href='$url'>$label</a></td>\n";
506     $desc = MTrackWiki::format_to_html($rep->description);
507     echo "<td class='desc'>$desc</td>\n";
508     echo "</tr>\n";
509   }
510 }
511
512 echo "</tbody></table>\n";
513
514   if (!$USE_AJAX) {
515     mtrack_foot();
516   }
517
518 }