1 <?php # vim:ts=2:sw=2:et:
2 /* For licensing and copyright terms, see the file named LICENSE */
5 * Think about how to handle the nav stuff...
6 * -> all urls need to respond to direct requests.
7 * -> the direct request is always an ajax
13 // Browse.php - only for rendering the body..
14 // Tree.php - the actually tree..
16 require_once 'MTrackWeb.php';
18 class MTrackWeb_Browse extends MTrackWeb
29 var $elements = array();
31 var $showCreate = false; // show create repo.
32 var $canEditRepo = false;
35 var $canDeleteFork = false;
57 function get($pi='', $args = array())
59 $this->pi = $pi . (strlen($pi) ? $this->bootLoader->ext : '');
61 if (!isset($_REQUEST['ajax_body'])) {
62 $this->title = "Browse: " . $this->pi;
65 $this->masterTemplate = 'tree.html';
66 //DB_DataObject::debugLevel(1);
71 $this->repo = DB_DataObject::factory('mtrack_repos');
72 $file = $this->repo->loadFromPath($this->pi);
73 if (!$this->repo->id) {
76 // if we have an active project.. enforce it..
77 if ($this->currentProject() && $this->repo && $this->repo->project_id != $this->currentProject()) {
78 $this->repo = false; // no repo..
85 if (isset($_GET['jump']) && strlen($_GET['jump'])) {
86 list($this->object, $this->ident) = explode(':', $_GET['jump'], 2);
90 if (!$this->projectPerm($this->repo->project_id, 'MTrack.Repos', 'S')) {
91 return HTML_FlexyFramework::run('Noperm'); // noperm = loggedin -> need more perms / not.. try loggin in..
94 $this->bdata = $this->getBrowseData($this->repo, $file, $this->object, $this->ident);
95 if (isset($this->bdata->err) ) {
96 throw new Exception($this->bdata->err);
98 require_once 'MTrack/Wiki/Item.php';
99 //make sure we can render..
100 MTrack_Wiki_Item::$repo = $this->repo->impl();
102 //$this->repo = (count($crumbs) > 2) ? MTrackSCM::factory($this->pi) : null ;
109 //$this->bdata = mtrack_cache(
110 // array($this,'getBrowseData'),
111 // array($this->repo, $this->pi, $this->object, $this->ident)
114 $this->basename = basename($file);
116 $crumbs = !strlen($file) || dirname($file) == '.' || !strlen(dirname($file)) ? array() :
117 explode('/', dirname($file));
120 $this->crumbs = array();
126 foreach($crumbs as $path)
131 $location .= strlen($location) ? '/' : '';
132 $location .= strlen($path) ? urlencode($path) : '';
133 $c->location = $location;
134 $this->crumbs[] = $c;
136 // echo '<PRE>';print_R($this->crumbs);
138 if ($this->bdata && count($this->bdata->jumps)) {
139 require_once 'HTML/Template/Flexy/Element.php';
140 $this->elements['jump'] = new HTML_Template_Flexy_Element('select');
141 // print_r($bdata->jumps);
142 $this->elements['jump']->setOptions($this->bdata->jumps);
149 if (MTrackACL::hasAllRights('Browser', 'create')) {
150 some users may have rights to create repos that belong to projects.
151 * Determine that list of projects here, because we need it for both
152 * the fork and new repo cases */
154 $owners = array("user:{$this->authUser->userid}" => $this->authUser->userid);
155 $q = MTrackDB::q( 'select projid, shortname, name from projects order by ordinal') ;
156 foreach ($q->fetchAll(PDO::FETCH_ASSOC) as $row) {
157 if (MTrackACL::hasAllRights("project:". $row['projid'], 'modify')) {
158 $owners["project:". $row['shortname']] = $row['shortname'];
161 $this->showCreate = count($owners) > 1 ? true : false;
162 require_once 'HTML/Template/Flexy/Element.php';
163 $this->elements['repo:parent'] = new HTML_Template_Flexy_Element('select');
164 $this->elements['repo:parent']->setOptions($owners);
169 MTrackACL::requireAllRights("repo:{$this->repo->id}", 'read');
171 // this looks buggy..
173 if ( $this->repo->canFork() &&
174 MTrackACL::hasAllRights('Browser', 'fork') &&
175 MTrackConfig::get('repos', 'allow_user_repo_creation')
178 $this->canFork = true;
179 $this->forkname = $this->repo->shortname;
181 if ("{$this->authUser->userid}/{$this->repo->shortname}" == $this->repo->getBrowseRootName()) {
182 // if this is mine already, make a "more unique" name for my new fork
183 $this->forkname = $this->repo->shortname . '2';
187 if ( $this->repo->parent &&
188 MTrackACL::hasAllRights("repo:{$this->repo->id}", "delete")
191 $this->canDeleteFork = true;
194 if (MTrackACL::hasAllRights("repo:{$this->repo->id}", "modify")) {
195 $this->canEditRepo = true;
197 // watch UI is different in this version..
198 // MTrackWatch::renderWatchUI('repo', $repo->id);
202 /// non repo options..
206 $this->dirname = $this->repo ? $this->repo->displayName() : '';
209 $this->dirname .= '/'. $file;
211 $this->up = strlen($file) ? dirname($this->dirname) : '';
212 $this->jump = isset($_GET['jump']) ? $_GET['jump'] : '';
215 return $this->noRepo(explode('/', $pi));
218 //print_r($this); exit;
223 function noRepo($crumbs)
226 /*if ( MTrackACL::hasAllRights('Browser', 'fork')
227 && MTrackConfig::get('repos', 'allow_user_repo_creation')
230 $repotypes = array();
232 foreach (MTrack_Repo::getAvailableSCMs() as $t => $r) {
233 $d = $r->getSCMMetaData();
234 $repotypes[$t] = $d['name'];
236 require_once 'HTML/Template/Flexy/Element.php';
237 $this->elements['repo:type'] = new HTML_Template_Flexy_Element('select');
238 $this->elements['repo:type']->setOptions($repotypes);
241 // DB_DataObject::debugLevel(1);
242 $mine = $this->authUser ? "user:{$this->authUser->id}" : '';
244 $do = DB_DataObject::factory('mtrack_repos');
247 $crumbs[0] = empty($crumbs[0]) ? 'default' : $crumbs[0] ;
249 if (count($crumbs) == 1 && $crumbs[0] != 'default') {
251 $do->whereAdd("parent like('%:". $do->escape($crumbs[0]). "')"); // subset
253 } else if (count($crumbs) == 1 && $crumbs[0] == 'default') {
255 $do->whereAdd(" parent = ''"); // top level..
257 /* looking for top level items */
260 /* have my own repos bubble up */
261 if ($this->authUser) {
262 $do->orderBy("CASE WHEN parent = '" . $do->escape('user:' . $this->authUser->id) . "' THEN 0 ELSE 1 END, shortname ASC");
264 $do->orderBy("shortname ASC");
267 if ($this->currentProject()) {
268 $do->project_id = $this->currentProject();
270 // FIXME -> permissions on repositories goes here..
271 //$do->ensurePerm($this->authUser);
274 $this->repos= array();
275 while ($do->fetch()) {
277 if (!$this->projectPerm($do->project_id, 'MTrack.Repos', 'S')) {
281 $this->repos[]= clone($do);
293 function getBrowseData($repo, $pi, $object, $ident, $hashes = false)
295 $data = new StdClass;
296 $data->dirs = array();
297 $data->files = array();
298 $data->jumps = array();
303 $branches = $repo->getBranches();
304 $tags = $repo->getTags();
305 if (count($branches) + count($tags)) {
306 $jumps = array("" => "- Select Branch / Tag - ");
307 if (is_array($branches)) {
308 foreach ($branches as $name => $notcare) {
309 $jumps["branch:$name"] = "Branch: $name";
312 if (is_array($tags)) {
313 foreach ($tags as $name => $notcare) {
314 $jumps["tag:$name"] = "Tag: $name";
317 $data->jumps = $jumps;
325 $ents = $repo->readdir($pi, $object, $ident);
326 if ($hashes === false) {
327 $ents = $this->cachedChangeEvent($ents);
329 // we are filling the cache..
330 return $this->cachedChangeEventFill($ents, $hashes);
334 foreach ($ents as $file) {
335 if (isset($file->hash)) {
336 $needLog[] = $file->hash;
339 $basename = basename($file->name);
341 $dirs[$basename] = $file;
343 $files[$basename] = $file;
347 uksort($files, 'strnatcmp');
348 uksort($dirs, 'strnatcmp');
351 $data->dirs = array_values($dirs);
352 $data->files = array_values($files);
354 $this->repopath = $this->repo->displayName();
355 $this->needLog = $needLog;
361 * New version of Directory listing
362 * Basically fetching the last change is very slow...
363 * - 1st run through -> fetch all the change events that we have
364 * for ones we do not have, fetch them later..
367 function cachedChangeEvent($ar)
369 // fetch a list of hashes...
373 $e->basename = basename($e->name);
374 // remove e->rev as it's not valid..
380 $q = DB_DataObject::factory('mtrack_clcache');
381 $q->whereAddIn('rev', array_keys($map), 'string');
382 $q->repo_id = $this->repo->id;
383 $revs = $q->fetchAll('rev', 'sobject');
384 $impl = $this->repo->impl();
386 foreach($revs as $hash => $sobject) {
387 $event = $impl->commitLogToEvent($sobject);
392 $event->is_dir = $map[$hash]->is_dir;
393 $event->name = $map[$hash]->name;
394 $event->basename = $map[$hash]->basename;
396 $map[$hash] = $event; // this was previous only done for directories??? why???
398 return array_values($map);
401 function cachedChangeEventFill($ar,$hashes)
404 $impl = $this->repo->impl();
407 if (!in_array($e->hash,$hashes)) {
411 // there is a small chance that concurrent users are looking for the same info..
412 $q = DB_DataObject::factory('mtrack_clcache');
414 $q->repo_id = $this->repo->id;
416 if ($q->find(true)) {
417 $event = $impl->commitLogToEvent($q->sobject);
419 $ent = $this->repo->history($e->name, 1, 'rev', $e->rev);
427 $q = DB_DataObject::factory('mtrack_clcache');
429 $q->repo_id = $this->repo->id;
430 $q->sobject = $event->commit;
434 // only copy a few essentials from ent, as we will send it back via json.
435 // these all need escaoing..
437 $add->changelog = $event->changelogOneToHtml($this->link);
438 $add->age = $event->ctimeToHtml($this->link);
439 $add->basename = basename($e->name);
440 $add->changeby = htmlspecialchars($event->changeby);
441 $add->rev = '<a class="changesetlink browse-link" href="'.
442 htmlspecialchars($this->baseURL) .
444 htmlspecialchars($this->repo->displayName()).
446 htmlspecialchars($event->rev) .
448 htmlspecialchars($event->rev) .
451 $map[$e->hash] = $add;
462 $this->pi = $pi . (strlen($pi) ? $this->bootLoader->ext : '');
466 $this->repo = DB_DataObject::factory('mtrack_repos');
467 $file = $this->repo->loadFromPath($this->pi);
468 if (!$this->repo->id) {
469 $this->jerr("INVALID URL");
472 $this->object = null;
475 if (isset($_GET['jump']) && strlen($_GET['jump'])) {
476 list($this->object, $this->ident) = explode(':', $_GET['jump'], 2);
480 $this->jerr("INVALID URL");
482 if (!$this->projectPerm($this->repo->project_id, 'MTrack.Repos', 'S')) {
483 $this->jerr("INVALID URL");
485 if (empty($_POST['hashes'])) {
486 $this->jerr("INVALID URL");
489 $ar = $this->getBrowseData($this->repo, $file, $this->object, $this->ident, explode(',', $_POST['hashes']));