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;
54 $this->pi = $pi . (strlen($pi) ? $this->bootLoader->ext : '');
56 if (!isset($_REQUEST['ajax_body'])) {
57 $this->title = "Browse: " . $this->pi;
60 $this->masterTemplate = 'tree.html';
61 //DB_DataObject::debugLevel(1);
66 $this->repo = DB_DataObject::factory('mtrack_repos');
67 $file = $this->repo->loadFromPath($this->pi);
68 if (!$this->repo->id) {
71 // if we have an active project.. enforce it..
72 if ($this->currentProject() && $this->repo && $this->repo->project_id != $this->currentProject()) {
73 $this->repo = false; // no repo..
80 if (isset($_GET['jump']) && strlen($_GET['jump'])) {
81 list($this->object, $this->ident) = explode(':', $_GET['jump'], 2);
85 if (!$this->projectPerm($this->repo->project_id, 'MTrack.Repos', 'S')) {
86 return HTML_FlexyFramework::run('Noperm'); // noperm = loggedin -> need more perms / not.. try loggin in..
89 $this->bdata = $this->getBrowseData($this->repo, $file, $this->object, $this->ident);
90 if (isset($this->bdata->err) ) {
91 throw new Exception($this->bdata->err);
93 require_once 'MTrack/Wiki/Item.php';
94 //make sure we can render..
95 MTrack_Wiki_Item::$repo = $this->repo->impl();
97 //$this->repo = (count($crumbs) > 2) ? MTrackSCM::factory($this->pi) : null ;
104 //$this->bdata = mtrack_cache(
105 // array($this,'getBrowseData'),
106 // array($this->repo, $this->pi, $this->object, $this->ident)
109 $this->basename = basename($file);
111 $crumbs = !strlen($file) || dirname($file) == '.' || !strlen(dirname($file)) ? array() :
112 explode('/', dirname($file));
115 $this->crumbs = array();
121 foreach($crumbs as $path)
126 $location .= strlen($location) ? '/' : '';
127 $location .= strlen($path) ? urlencode($path) : '';
128 $c->location = $location;
129 $this->crumbs[] = $c;
131 // echo '<PRE>';print_R($this->crumbs);
133 if ($this->bdata && count($this->bdata->jumps)) {
134 require_once 'HTML/Template/Flexy/Element.php';
135 $this->elements['jump'] = new HTML_Template_Flexy_Element('select');
136 // print_r($bdata->jumps);
137 $this->elements['jump']->setOptions($this->bdata->jumps);
144 if (MTrackACL::hasAllRights('Browser', 'create')) {
145 some users may have rights to create repos that belong to projects.
146 * Determine that list of projects here, because we need it for both
147 * the fork and new repo cases */
149 $owners = array("user:{$this->authUser->userid}" => $this->authUser->userid);
150 $q = MTrackDB::q( 'select projid, shortname, name from projects order by ordinal') ;
151 foreach ($q->fetchAll(PDO::FETCH_ASSOC) as $row) {
152 if (MTrackACL::hasAllRights("project:". $row['projid'], 'modify')) {
153 $owners["project:". $row['shortname']] = $row['shortname'];
156 $this->showCreate = count($owners) > 1 ? true : false;
157 require_once 'HTML/Template/Flexy/Element.php';
158 $this->elements['repo:parent'] = new HTML_Template_Flexy_Element('select');
159 $this->elements['repo:parent']->setOptions($owners);
164 MTrackACL::requireAllRights("repo:{$this->repo->id}", 'read');
166 // this looks buggy..
168 if ( $this->repo->canFork() &&
169 MTrackACL::hasAllRights('Browser', 'fork') &&
170 MTrackConfig::get('repos', 'allow_user_repo_creation')
173 $this->canFork = true;
174 $this->forkname = $this->repo->shortname;
176 if ("{$this->authUser->userid}/{$this->repo->shortname}" == $this->repo->getBrowseRootName()) {
177 // if this is mine already, make a "more unique" name for my new fork
178 $this->forkname = $this->repo->shortname . '2';
182 if ( $this->repo->parent &&
183 MTrackACL::hasAllRights("repo:{$this->repo->id}", "delete")
186 $this->canDeleteFork = true;
189 if (MTrackACL::hasAllRights("repo:{$this->repo->id}", "modify")) {
190 $this->canEditRepo = true;
192 // watch UI is different in this version..
193 // MTrackWatch::renderWatchUI('repo', $repo->id);
197 /// non repo options..
201 $this->dirname = $this->repo ? $this->repo->displayName() : '';
204 $this->dirname .= '/'. $file;
206 $this->up = strlen($file) ? dirname($this->dirname) : '';
207 $this->jump = isset($_GET['jump']) ? $_GET['jump'] : '';
210 return $this->noRepo(explode('/', $pi));
213 //print_r($this); exit;
218 function noRepo($crumbs)
221 /*if ( MTrackACL::hasAllRights('Browser', 'fork')
222 && MTrackConfig::get('repos', 'allow_user_repo_creation')
225 $repotypes = array();
227 foreach (MTrack_Repo::getAvailableSCMs() as $t => $r) {
228 $d = $r->getSCMMetaData();
229 $repotypes[$t] = $d['name'];
231 require_once 'HTML/Template/Flexy/Element.php';
232 $this->elements['repo:type'] = new HTML_Template_Flexy_Element('select');
233 $this->elements['repo:type']->setOptions($repotypes);
236 // DB_DataObject::debugLevel(1);
237 $mine = $this->authUser ? "user:{$this->authUser->id}" : '';
239 $do = DB_DataObject::factory('mtrack_repos');
242 $crumbs[0] = empty($crumbs[0]) ? 'default' : $crumbs[0] ;
244 if (count($crumbs) == 1 && $crumbs[0] != 'default') {
246 $do->whereAdd("parent like('%:". $do->escape($crumbs[0]). "')"); // subset
248 } else if (count($crumbs) == 1 && $crumbs[0] == 'default') {
250 $do->whereAdd(" parent = ''"); // top level..
252 /* looking for top level items */
255 /* have my own repos bubble up */
256 if ($this->authUser) {
257 $do->orderBy("CASE WHEN parent = '" . $do->escape('user:' . $this->authUser->id) . "' THEN 0 ELSE 1 END, shortname ASC");
259 $do->orderBy("shortname ASC");
262 if ($this->currentProject()) {
263 $do->project_id = $this->currentProject();
265 // FIXME -> permissions on repositories goes here..
266 //$do->ensurePerm($this->authUser);
269 $this->repos= array();
270 while ($do->fetch()) {
272 if (!$this->projectPerm($do->project_id, 'MTrack.Repos', 'S')) {
276 $this->repos[]= clone($do);
288 function getBrowseData($repo, $pi, $object, $ident, $hashes = false)
290 $data = new StdClass;
291 $data->dirs = array();
292 $data->files = array();
293 $data->jumps = array();
298 $branches = $repo->getBranches();
299 $tags = $repo->getTags();
300 if (count($branches) + count($tags)) {
301 $jumps = array("" => "- Select Branch / Tag - ");
302 if (is_array($branches)) {
303 foreach ($branches as $name => $notcare) {
304 $jumps["branch:$name"] = "Branch: $name";
307 if (is_array($tags)) {
308 foreach ($tags as $name => $notcare) {
309 $jumps["tag:$name"] = "Tag: $name";
312 $data->jumps = $jumps;
320 $ents = $repo->readdir($pi, $object, $ident);
321 if ($hashes === false) {
322 $ents = $this->cachedChangeEvent($ents);
324 // we are filling the cache..
325 return $this->cachedChangeEventFill($ents, $hashes);
329 foreach ($ents as $file) {
330 if (isset($file->hash)) {
331 $needLog[] = $file->hash;
334 $basename = basename($file->name);
336 $dirs[$basename] = $file;
338 $files[$basename] = $file;
342 uksort($files, 'strnatcmp');
343 uksort($dirs, 'strnatcmp');
346 $data->dirs = array_values($dirs);
347 $data->files = array_values($files);
349 $this->repopath = $this->repo->displayName();
350 $this->needLog = $needLog;
356 * New version of Directory listing
357 * Basically fetching the last change is very slow...
358 * - 1st run through -> fetch all the change events that we have
359 * for ones we do not have, fetch them later..
362 function cachedChangeEvent($ar)
364 // fetch a list of hashes...
368 $e->basename = basename($e->name);
369 // remove e->rev as it's not valid..
375 $q = DB_DataObject::factory('mtrack_clcache');
376 $q->whereAddIn('rev', array_keys($map), 'string');
377 $q->repo_id = $this->repo->id;
378 $revs = $q->fetchAll('rev', 'sobject');
379 $impl = $this->repo->impl();
381 foreach($revs as $hash => $sobject) {
382 $event = $impl->commitLogToEvent($sobject);
384 $event->is_dir = $map[$hash]->is_dir;
385 $event->name = $map[$hash]->name;
386 $event->basename = $map[$hash]->basename;
387 $map[$hash] = $event; // this was previous only done for directories??? why???
389 return array_values($map);
392 function cachedChangeEventFill($ar,$hashes)
395 $impl = $this->repo->impl();
398 if (!in_array($e->hash,$hashes)) {
402 // there is a small chance that concurrent users are looking for the same info..
403 $q = DB_DataObject::factory('mtrack_clcache');
405 $q->repo_id = $this->repo->id;
407 if ($q->find(true)) {
408 $event = $impl->commitLogToEvent($q->sobject);
410 $ent = $this->repo->history($e->name, 1, 'rev', $e->rev);
418 $q = DB_DataObject::factory('mtrack_clcache');
420 $q->repo_id = $this->repo->id;
421 $q->sobject = $event->commit;
425 // only copy a few essentials from ent, as we will send it back via json.
426 // these all need escaoing..
428 $add->changelog = $event->changelogOneToHtml($this->link);
429 $add->age = $event->ctimeToHtml($this->link);
430 $add->basename = basename($e->name);
431 $add->changeby = htmlspecialchars($event->changeby);
432 $add->rev = '<a class="changesetlink browse-link" href="'.
433 htmlspecialchars($this->baseURL) .
435 htmlspecialchars($this->repo->displayName()).
437 htmlspecialchars($event->rev) .
439 htmlspecialchars($event->rev) .
442 $map[$e->hash] = $add;
453 $this->pi = $pi . (strlen($pi) ? $this->bootLoader->ext : '');
457 $this->repo = DB_DataObject::factory('mtrack_repos');
458 $file = $this->repo->loadFromPath($this->pi);
459 if (!$this->repo->id) {
460 $this->jerr("INVALID URL");
463 $this->object = null;
466 if (isset($_GET['jump']) && strlen($_GET['jump'])) {
467 list($this->object, $this->ident) = explode(':', $_GET['jump'], 2);
471 $this->jerr("INVALID URL");
473 if (!$this->projectPerm($this->repo->project_id, 'MTrack.Repos', 'S')) {
474 $this->jerr("INVALID URL");
476 if (empty($_POST['hashes'])) {
477 $this->jerr("INVALID URL");
480 $ar = $this->getBrowseData($this->repo, $file, $this->object, $this->ident, explode(',', $_POST['hashes']));