PageRenderTime 72ms CodeModel.GetById 23ms RepoModel.GetById 2ms app.codeStats 0ms

/web/browse.php

https://bitbucket.org/yoander/mtrack
PHP | 546 lines | 473 code | 56 blank | 17 comment | 80 complexity | 2bea17eabe48ebe1398a1ba2cc38718d MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
  1. <?php # vim:ts=2:sw=2:et:
  2. /* For licensing and copyright terms, see the file named LICENSE */
  3. include '../inc/common.php';
  4. $USE_AJAX = false;
  5. MTrackACL::requireAllRights('Browser', 'read');
  6. $pi = mtrack_get_pathinfo(true);
  7. $crumbs = MTrackSCM::makeBreadcrumbs($pi);
  8. if (!strlen($pi)) {
  9. $pi = '/';
  10. }
  11. if (count($crumbs) > 2) {
  12. $repo = MTrackSCM::factory($pi);
  13. } else {
  14. $repo = null;
  15. }
  16. if (!isset($_GET['_'])) {
  17. $AJAX = false;
  18. } else {
  19. $AJAX = true;
  20. }
  21. function one_line_cl($changelog)
  22. {
  23. list($one) = explode("\n", $changelog);
  24. return rtrim($one, " \r\n");
  25. }
  26. function get_browse_data($repo, $pi, $object, $ident)
  27. {
  28. global $ABSWEB;
  29. $data = new stdclass;
  30. $data->dirs = array();
  31. $data->files = array();
  32. $data->jumps = array();
  33. if (!$repo) {
  34. return $data;
  35. }
  36. $branches = $repo->getBranches();
  37. $tags = $repo->getTags();
  38. if (count($branches) + count($tags)) {
  39. $jumps = array("" => "- Select Branch / Tag - ");
  40. if (is_array($branches)) {
  41. foreach ($branches as $name => $notcare) {
  42. $jumps["branch:$name"] = "Branch: $name";
  43. }
  44. }
  45. if (is_array($tags)) {
  46. foreach ($tags as $name => $notcare) {
  47. $jumps["tag:$name"] = "Tag: $name";
  48. }
  49. }
  50. $data->jumps = $jumps;
  51. }
  52. $files = array();
  53. $dirs = array();
  54. if ($repo) {
  55. try {
  56. $ents = $repo->readdir($pi, $object, $ident);
  57. } catch (Exception $e) {
  58. // Typically a freshly created repo
  59. $ents = array();
  60. $data->err = $e->getMessage();
  61. }
  62. foreach ($ents as $file) {
  63. $basename = basename($file->name);
  64. if ($file->is_dir) {
  65. $dirs[$basename] = $file;
  66. } else {
  67. $files[$basename] = $file;
  68. }
  69. }
  70. }
  71. uksort($files, 'strnatcmp');
  72. uksort($dirs, 'strnatcmp');
  73. $data->files = array();
  74. $data->dirs = array();
  75. $urlbase = $ABSWEB . 'browse.php';
  76. $pathbase = '/' . $repo->getBrowseRootName();
  77. $urlbase .= $pathbase;
  78. foreach ($dirs as $basename => $file) {
  79. $ent = $file->getChangeEvent();
  80. $url = $urlbase . '/' . $file->name;
  81. $d = new stdclass;
  82. $d->url = $url;
  83. $d->basename = $basename;
  84. $d->rev = $ent->rev;
  85. $d->ctime = $ent->ctime;
  86. $d->changeby = $ent->changeby;
  87. $d->changelog = one_line_cl($ent->changelog);
  88. $data->dirs[] = $d;
  89. }
  90. foreach ($files as $basename => $file) {
  91. $ent = $file->getChangeEvent();
  92. $url = $ABSWEB . 'file.php' . $pathbase .
  93. '/' . $file->name . '?rev=' . $ent->rev;
  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. $data->files[] = $d;
  102. }
  103. return $data;
  104. }
  105. if (isset($_GET['jump']) && strlen($_GET['jump'])) {
  106. list($object, $ident) = explode(':', $_GET['jump'], 2);
  107. } else {
  108. $object = null;
  109. $ident = null;
  110. }
  111. if ($USE_AJAX && !$AJAX) {
  112. mtrack_head("Browse $pi");
  113. // Since big dirs can take a while to gather the browse data,
  114. // We want to show *something* to the user while we wait for
  115. // the data to come in
  116. $g = $_GET;
  117. $g['_'] = '_';
  118. $url = $_SERVER['REQUEST_URI'] . '?' . http_build_query($g);
  119. echo <<<HTML
  120. <div id='browsediv'>
  121. <p>Loading browse data, please wait</p>
  122. </div>
  123. <script>
  124. \$(document).ready(function () {
  125. \$('#browsediv').load('$url');
  126. });
  127. </script>
  128. HTML;
  129. mtrack_foot();
  130. } else {
  131. if (!$USE_AJAX) {
  132. mtrack_head("Browse $pi");
  133. }
  134. $bdata = mtrack_cache('get_browse_data',
  135. array($repo, $pi, $object, $ident));
  136. if (isset($bdata->err) && strlen($pi) > 1) {
  137. throw new Exception($bdata->err);
  138. }
  139. /* Render a bread-crumb enabled location indicator */
  140. echo "<ul class='breadcrumb browselocation'>Location: ";
  141. $location = null;
  142. foreach ($crumbs as $i => $path) {
  143. if (!strlen($path)) {
  144. $path = '<i class="icon-home"></i> [root]';
  145. } else {
  146. $location .= '/' . urlencode($path);
  147. $path = htmlentities($path, ENT_QUOTES, 'utf-8');
  148. }
  149. if ($i == count($crumbs) - 1) {
  150. echo "<li class='active'>";
  151. } else {
  152. echo "<li>";
  153. }
  154. echo "<a href='{$ABSWEB}browse.php$location'>$path</a> ";
  155. if ($i < count($crumbs) - 1) {
  156. echo "<span class='divider'>/</span>";
  157. }
  158. echo "</li>";
  159. }
  160. echo "</ul>";
  161. if (count($bdata->jumps)) {
  162. echo "<form>";
  163. echo mtrack_select_box("jump", $bdata->jumps,
  164. isset($_GET['jump']) ? $_GET['jump'] : null);
  165. echo "<button class='btn' type='submit'>Choose</button></form>\n";
  166. }
  167. $me = mtrack_canon_username(MTrackAuth::whoami());
  168. if (MTrackACL::hasAllRights('Browser', 'create')) {
  169. /* some users may have rights to create repos that belong to projects.
  170. * Determine that list of projects here, because we need it for both
  171. * the fork and new repo cases */
  172. $owners = array("user:$me" => $me);
  173. foreach (MTrackDB::q(
  174. 'select projid, shortname, name from projects order by ordinal')
  175. as $row)
  176. {
  177. if (MTrackACL::hasAllRights("project:$row[0]", 'modify')) {
  178. $owners['project:' . $row[1]] = $row[1];
  179. }
  180. }
  181. if (count($owners) > 1) {
  182. $owners = mtrack_select_box('parent', $owners, null, true);
  183. } else {
  184. $owners = "<input type='hidden' name='parent' value='" .
  185. htmlentities($me, ENT_QUOTES, 'utf-8') . "'>";
  186. }
  187. }
  188. if ($repo) {
  189. MTrackACL::requireAllRights("repo:$repo->repoid", 'read');
  190. $description = MTrackWiki::format_to_html($repo->description);
  191. $url = $repo->getCheckoutCommand();
  192. echo "<div class='repodesc'>$description</div>";
  193. if (strlen($url)) {
  194. echo "<div class='checkout'>\n";
  195. echo "Use the following command to obtain a working copy:<br>";
  196. echo "<pre>\$ $url</pre>";
  197. echo "</div>\n";
  198. }
  199. echo "<div id='repobuttons'>\n";
  200. if ($repo->canFork() && MTrackACL::hasAllRights('Browser', 'fork')
  201. && MTrackConfig::get('repos', 'allow_user_repo_creation')) {
  202. $forkname = "$me/$repo->shortname";
  203. if ($forkname == $repo->getBrowseRootName()) {
  204. /* if this is mine already, make a "more unique" name for my new fork */
  205. $forkname = $repo->shortname . '2';
  206. } else {
  207. $forkname = $repo->shortname;
  208. }
  209. $forkname = htmlentities($forkname, ENT_QUOTES, 'utf-8');
  210. echo <<<HTML
  211. <div id='forkdialog' class='modal hide fade'>
  212. <div class='modal-header'>
  213. <a class='close' data-dismiss='modal'>&times;</a>
  214. <h3>Fork a repo</h3>
  215. </div>
  216. <div class='modal-body'>
  217. <p>
  218. A fork is your own copy of a repo that is stored and maintained
  219. on the server.
  220. </p>
  221. <p>
  222. If all you want to do is obtain a working copy so that you can
  223. collaborate on this repo, you should not create a fork.
  224. </p>
  225. <p>
  226. You may want to fork if you want the server to keep your work backed up,
  227. or to collaborate with others on work that you want to share
  228. with this repo later on.
  229. </p>
  230. <p>
  231. Choose a name for your fork:<br>
  232. $owners <input type='text' name='name' value='$forkname'>
  233. <br>
  234. <br>
  235. <br>
  236. <br>
  237. </p>
  238. </div>
  239. <div class='modal-footer'>
  240. <button class='btn' data-dismiss='modal'>Cancel</button>
  241. <button id='createforkbtn'
  242. class='btn btn-primary'>Fork</button>
  243. </div>
  244. </div>
  245. <button id='forkbtn' class='btn' type='button'
  246. data-toggle='modal' data-target='#forkdialog'><i class="icon-random"></i> Fork</button>
  247. <script>
  248. $(document).ready(function () {
  249. function capture_error(model, resp) {
  250. var err;
  251. if (!_.isObject(resp)) {
  252. err = resp;
  253. } else {
  254. err = resp.statusText;
  255. try {
  256. var r = JSON.parse(resp.responseText);
  257. err = r.message;
  258. } catch (e) {
  259. }
  260. }
  261. $('<div class="alert alert-danger">' +
  262. "<a class='close' data-dismiss='alert'>&times;</a>" +
  263. err + '</div>').
  264. appendTo('#forkdialog div.modal-body');
  265. }
  266. $('#createforkbtn').click(function () {
  267. var dlg = $('#forkdialog');
  268. var owner = $('select[name=parent]', dlg).val();
  269. var name = $('input[name=name]', dlg).val();
  270. var R = new MTrackRepo;
  271. R.save({
  272. parent: owner,
  273. shortname: name,
  274. clonedfrom: $repo->repoid,
  275. }, {
  276. success: function (model, resp) {
  277. window.location = ABSWEB + "browse.php/" + model.get('browsepath');
  278. },
  279. error: capture_error
  280. });
  281. });
  282. });
  283. </script>
  284. HTML
  285. ;
  286. }
  287. $mine = "user:$me";
  288. if (MTrackACL::hasAllRights("repo:$repo->repoid", "modify")) {
  289. $repojson = json_encode(MTrackAPI::invoke(
  290. 'GET', "/repo/properties/$repo->repoid")->result);
  291. echo <<<HTML
  292. <button id='editrepobtn' class='btn'><i class="icon-edit"></i> Edit</button>
  293. <script>
  294. $(document).ready(function () {
  295. $('#editrepobtn').click(function () {
  296. var R = new MTrackRepo($repojson);
  297. var V = new MTrackRepoEditView({
  298. model: R
  299. });
  300. V.show(function (model) {
  301. if (model) {
  302. window.location = ABSWEB + 'browse.php/' + model.get('browsepath');
  303. } else {
  304. // deleted
  305. window.location = ABSWEB + 'browse.php';
  306. }
  307. });
  308. });
  309. });
  310. </script>
  311. HTML
  312. ;
  313. }
  314. MTrackWatch::renderWatchUI('repo', $repo->repoid);
  315. echo "</div>\n"; # end of repobuttons
  316. echo "<br>\n<a href='{$ABSWEB}log.php/{$repo->getBrowseRootName()}/$pi'>Show History</a><br>\n";
  317. }
  318. if (!$repo && MTrackACL::hasAllRights('Browser', 'fork')
  319. && MTrackConfig::get('repos', 'allow_user_repo_creation')) {
  320. echo <<<HTML
  321. <button id='newrepobtn' class='btn btn-primary'
  322. type='button'><i class='icon-plus icon-white'></i> New Repository</button>
  323. <br>
  324. <script>
  325. $(document).ready(function () {
  326. $('#newrepobtn').click(function () {
  327. var R = new MTrackRepo;
  328. var V = new MTrackRepoEditView({
  329. model: R
  330. });
  331. V.show(function () {
  332. window.location = ABSWEB + 'browse.php/' + R.get('browsepath');
  333. });
  334. });
  335. });
  336. </script>
  337. HTML
  338. ;
  339. }
  340. echo "<br>\n";
  341. ?>
  342. <table class='listing' id='dirlist'>
  343. <thead>
  344. <tr>
  345. <?php
  346. if (!$repo) {
  347. ?>
  348. <th class='name' width='1%'>Name</th>
  349. <th class='desc'>Description</th>
  350. <?php
  351. } else {
  352. ?>
  353. <th class='name' width='1%'>Name</th>
  354. <th class='rev' width='1%'>Revision</th>
  355. <th class='age' width='1%'>Age</th>
  356. <th class='change'>Last Change</th>
  357. <?php
  358. }
  359. ?>
  360. </tr>
  361. </thead>
  362. <tbody>
  363. <?php
  364. $even = 1;
  365. if (count($crumbs) > 1) {
  366. $class = $even++ % 2 ? 'even' : 'odd';
  367. $url = $ABSWEB . 'browse.php' . dirname(mtrack_get_pathinfo(true));
  368. if (isset($_GET['jump'])) {
  369. $url .= '?jump=' . urlencode($_GET['jump']);
  370. }
  371. $url = htmlentities($url, ENT_QUOTES, 'utf-8');
  372. echo "<tr class='$class'>\n";
  373. echo "<td class='name'><a class='parent' href='$url'>.. [up]</a></td>";
  374. if ($repo) {
  375. echo "<td class='rev'></td>\n";
  376. echo "<td class='age'></td>\n";
  377. echo "<td class='change'></td>\n";
  378. } else {
  379. echo "<td class='desc'></td>\n";
  380. }
  381. echo "</tr>\n";
  382. }
  383. foreach ($bdata->dirs as $d) {
  384. $class = $even++ % 2 ? 'even' : 'odd';
  385. $url = $d->url;
  386. if (isset($_GET['jump'])) {
  387. $url .= '?jump=' . urlencode($_GET['jump']);
  388. }
  389. $url = htmlentities($url, ENT_QUOTES, 'utf-8');
  390. echo "<tr class='$class'>\n";
  391. echo "<td class='name'><a class='dir' href='$url'>$d->basename</a></td>";
  392. echo "<td class='rev'>" . mtrack_changeset($d->rev, $repo) . "</td>\n";
  393. echo "<td class='age'>" . mtrack_date($d->ctime) . "</td>\n";
  394. echo "<td class='change'>" .
  395. mtrack_username($d->changeby, array('size' => 16)) . ": " .
  396. MTrackWiki::format_to_oneliner($d->changelog) . "</td>\n";
  397. echo "</tr>\n";
  398. }
  399. $README = array();
  400. foreach ($bdata->files as $d) {
  401. $class = $even++ % 2 ? 'even' : 'odd';
  402. $url = $d->url;
  403. if (isset($_GET['jump'])) {
  404. $url .= '&jump=' . urlencode($_GET['jump']);
  405. }
  406. $url = htmlentities($url, ENT_QUOTES, 'utf-8');
  407. echo "<tr class='$class'>\n";
  408. echo "<td class='name'><a class='file' href='$url'>$d->basename</a></td>";
  409. echo "<td class='rev'>" . mtrack_changeset($d->rev, $repo) . "</td>\n";
  410. echo "<td class='age'>" . mtrack_date($d->ctime) . "</td>\n";
  411. echo "<td class='change'>" .
  412. mtrack_username($d->changeby, array('size' => 16)) . ": " .
  413. MTrackWiki::format_to_oneliner($d->changelog) . "</td>\n";
  414. echo "</tr>\n";
  415. if (preg_match("/^README\.(md|mkd|markdown)$/i", $d->basename)) {
  416. $README['md'] = mtrack_get_pathinfo(true) . '/' . $d->basename;
  417. } else if (preg_match("/^README/i", $d->basename)) {
  418. $README['txt'] = mtrack_get_pathinfo(true) . '/' . $d->basename;
  419. }
  420. }
  421. if (!$repo) {
  422. $mine = 'user:' . mtrack_canon_username(MTrackAuth::whoami());
  423. $params = array();
  424. if (count($crumbs) == 2 && $crumbs[1] != 'default') {
  425. /* looking for a particular subset */
  426. $where = "parent like('%:' || ?)";
  427. $params[] = $crumbs[1];
  428. } else if (count($crumbs) == 2 && $crumbs[1] == 'default') {
  429. /* looking at system items */
  430. $where = "parent = ''";
  431. } else {
  432. /* looking for top level items */
  433. $where = "1 = 1";
  434. }
  435. /* have my own repos bubble up */
  436. $params[] = $mine;
  437. $sql = <<<SQL
  438. select repoid, parent, shortname, description
  439. from repos
  440. where $where
  441. order by
  442. case when parent = ? then 0 else 1 end,
  443. parent,
  444. shortname
  445. SQL
  446. ;
  447. $q = MTrackDB::get()->prepare($sql);
  448. $q->execute($params);
  449. foreach ($q->fetchAll(PDO::FETCH_OBJ) as $rep) {
  450. if (!MTrackACL::hasAnyRights("repo:$rep->repoid", 'read')) {
  451. continue;
  452. }
  453. $class = $even++ % 2 ? 'even' : 'odd';
  454. $url = $ABSWEB . 'browse.php/';
  455. $label = MTrackRepo::makeDisplayName($rep);
  456. $url .= $label;
  457. echo "<tr class='$class'>\n";
  458. echo "<td class='name'><a class='dir' href='$url'>$label</a></td>\n";
  459. $desc = MTrackWiki::format_to_html($rep->description);
  460. echo "<td class='desc'>$desc</td>\n";
  461. echo "</tr>\n";
  462. }
  463. }
  464. echo "</tbody></table>\n";
  465. if ($repo && count($README)) {
  466. function get_repo_content($name) {
  467. global $repo;
  468. $bits = explode('/', $name);
  469. array_shift($bits); /* empty due to leading slash */
  470. array_shift($bits); /* owner */
  471. array_shift($bits); /* repo name */
  472. $name = join($bits, '/');
  473. $file = $repo->file($name);
  474. return stream_get_contents($file->cat());
  475. }
  476. /* prefer to display a markdown formatted readme, otherwise just
  477. * show it as plain text */
  478. if (isset($README['md'])) {
  479. $readme = mtrack_markdown(get_repo_content($README['md']));
  480. } else {
  481. $readme = "<pre>" .
  482. htmlentities(get_repo_content($README['txt']), ENT_QUOTES, 'utf-8') .
  483. "</pre>";
  484. }
  485. echo "<br>$readme<br>";
  486. }
  487. if (!$USE_AJAX) {
  488. mtrack_foot();
  489. }
  490. }