MTrackWeb/Browse.php
[web.mtrack] / MTrackWeb / Browse.php
1 <?php # vim:ts=2:sw=2:et:
2 /* For licensing and copyright terms, see the file named LICENSE */
3
4 /**
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
8  *
9  *
10  *
11  */
12
13 // Browse.php - only for rendering the body..
14 // Tree.php - the actually tree..
15
16 require_once 'MTrackWeb.php';
17
18 class MTrackWeb_Browse extends MTrackWeb
19 {
20    
21     
22     
23     
24  
25     var $object;
26     var $ident;
27     
28     var $repo;
29     var $elements = array();
30     
31     var $showCreate = false; // show create repo.
32     var $canEditRepo = false;
33     
34     var $canFork = false;
35     var $canDeleteFork = false;
36     
37     var $forkname = '';
38     
39     var $bdata = false;
40   
41     var $up = '';
42     var $jump = '';
43     
44     function getAuth() 
45     {
46         parent::getAuth();
47         
48         return true;
49   
50     }
51  
52     function get($pi)
53     {
54         $this->pi =  $pi . (strlen($pi) ? $this->bootLoader->ext : '');
55        
56         if (!isset($_REQUEST['ajax_body'])) {
57             $this->title = "Browse: " . $this->pi;
58             return;
59         }
60         $this->masterTemplate = 'tree.html';
61         //DB_DataObject::debugLevel(1);
62        
63         //var_dump($pi);
64         
65          
66         $this->repo = DB_DataObject::factory('mtrack_repos');
67         $file = $this->repo->loadFromPath($this->pi);
68         if (!$this->repo->id) {
69             $this->repo = false;
70         }
71         
72         $this->object = null;
73         $this->ident = null;
74         
75         if (isset($_GET['jump']) && strlen($_GET['jump'])) {
76             list($this->object, $this->ident) = explode(':', $_GET['jump'], 2);
77         }
78         
79         if ($this->repo) {
80             if (!$this->projectPerm($this->repo->project_id, 'MTrack.Repos', 'S')) {
81                 return HTML_FlexyFramework::run('Noperm');  // noperm = loggedin -> need more perms / not.. try loggin in..
82             }
83             
84             $this->bdata = $this->getBrowseData($this->repo, $file, $this->object, $this->ident);
85             if (isset($this->bdata->err) ) {
86                 throw new Exception($this->bdata->err);
87             }
88             require_once 'MTrack/Wiki/Item.php';
89             //make sure we can render..
90             MTrack_Wiki_Item::$repo = $this->repo->impl();
91         }    
92         //$this->repo  = (count($crumbs) > 2) ?   MTrackSCM::factory($this->pi) : null ;
93          
94         
95         
96         
97        
98         
99         //$this->bdata = mtrack_cache(
100         //    array($this,'getBrowseData'), 
101         //    array($this->repo, $this->pi, $this->object, $this->ident)
102         //);
103
104         $this->basename  = basename($file);
105         $location = '';
106         $crumbs = !strlen($file) || dirname($file) == '.' || !strlen(dirname($file)) ? array() :
107             explode('/', dirname($file));
108         
109         
110         $this->crumbs = array();
111         
112         if (!$this->repo) {
113             $crumbs = array();
114         }
115         
116         foreach($crumbs as $path) 
117         {
118              
119             $c = new StdClass;
120             $c->name =   $path ;
121             $location .=  strlen($location) ? '/' : '';
122             $location .=  strlen($path)  ?  urlencode($path)  : '';
123             $c->location = $location;
124             $this->crumbs[] = $c;
125         }
126        // echo '<PRE>';print_R($this->crumbs);
127          
128         if ($this->bdata && count($this->bdata->jumps)) {
129             require_once 'HTML/Template/Flexy/Element.php';
130             $this->elements['jump'] = new HTML_Template_Flexy_Element('select');
131            // print_r($bdata->jumps);
132             $this->elements['jump']->setOptions($this->bdata->jumps);
133         }
134         
135         
136         
137         
138          /*
139         if (MTrackACL::hasAllRights('Browser', 'create')) {
140           some users may have rights to create repos that belong to projects.
141             * Determine that list of projects here, because we need it for both
142             * the fork and new repo cases */
143             /*
144             $owners = array("user:{$this->authUser->userid}" => $this->authUser->userid);
145             $q = MTrackDB::q( 'select projid, shortname, name from projects order by ordinal') ;
146             foreach ($q->fetchAll(PDO::FETCH_ASSOC) as $row) {
147                 if (MTrackACL::hasAllRights("project:". $row['projid'], 'modify')) {
148                     $owners["project:". $row['shortname']] = $row['shortname'];
149                 }
150             }
151             $this->showCreate = count($owners) > 1 ? true : false;
152             require_once 'HTML/Template/Flexy/Element.php';
153             $this->elements['repo:parent'] = new HTML_Template_Flexy_Element('select');
154             $this->elements['repo:parent']->setOptions($owners);
155             
156         }
157           
158         if ($this->repo) {
159             MTrackACL::requireAllRights("repo:{$this->repo->id}", 'read');
160              
161             // this looks buggy..
162            
163             if (    $this->repo->canFork() && 
164                     MTrackACL::hasAllRights('Browser', 'fork') &&
165                     MTrackConfig::get('repos', 'allow_user_repo_creation')
166                 ) {
167                     
168                 $this->canFork = true;
169                 $this->forkname = $this->repo->shortname;
170                 
171                 if ("{$this->authUser->userid}/{$this->repo->shortname}" == $this->repo->getBrowseRootName()) {
172                     // if this is mine already, make a "more unique" name for my new fork 
173                     $this->forkname = $this->repo->shortname . '2';
174                 }
175             }
176             
177             if (    $this->repo->parent &&
178                     MTrackACL::hasAllRights("repo:{$this->repo->id}", "delete")
179                 ) {
180                     
181                 $this->canDeleteFork = true;
182             }
183             
184             if (MTrackACL::hasAllRights("repo:{$this->repo->id}", "modify")) {
185                 $this->canEditRepo = true;
186             }
187             // watch UI is different in this version..
188             // MTrackWatch::renderWatchUI('repo', $repo->id);
189             
190         }
191          */
192         /// non repo options..
193         
194
195         
196         $this->dirname = $this->repo ? $this->repo->displayName() : '';
197         // up...
198         if (strlen($file)) {
199             $this->dirname .= '/'. $file;
200         }
201         $this->up =  strlen($file) ? dirname($this->dirname) : '';
202         $this->jump =  isset($_GET['jump']) ? $_GET['jump'] : '';
203     
204          if (!$this->repo) {
205             return $this->noRepo(explode('/', $pi));
206          
207         }
208         //print_r($this); exit;
209         
210           
211     }
212      
213     function noRepo($crumbs)
214     {
215         
216         /*if (  MTrackACL::hasAllRights('Browser', 'fork')
217             && MTrackConfig::get('repos', 'allow_user_repo_creation')
218             ){
219                 
220             $repotypes = array();
221             
222             foreach (MTrack_Repo::getAvailableSCMs() as $t => $r) {
223               $d = $r->getSCMMetaData();
224               $repotypes[$t] = $d['name'];
225             }
226             require_once 'HTML/Template/Flexy/Element.php';
227             $this->elements['repo:type'] = new HTML_Template_Flexy_Element('select');
228             $this->elements['repo:type']->setOptions($repotypes);
229         }
230         */
231        // DB_DataObject::debugLevel(1);
232         $mine = $this->authUser ? "user:{$this->authUser->id}" : '';
233         
234         $do = DB_DataObject::factory('mtrack_repos');
235         $params = array();
236          
237         $crumbs[0]  = empty($crumbs[0]) ? 'default' : $crumbs[0] ;
238         
239         if (count($crumbs) == 1 && $crumbs[0] != 'default') {
240         
241              $do->whereAdd("parent like('%:". $do->escape($crumbs[0]). "')"); // subset
242              
243          } else if (count($crumbs) == 1 && $crumbs[0] == 'default') {
244          
245             $do->whereAdd(" parent = ''"); // top level..
246          } else {
247              /* looking for top level items */
248              ///$where = "1 = 1";
249          }
250          /* have my own repos bubble up */
251         if ($this->authUser) {
252             $do->orderBy("CASE WHEN  parent = '" . $do->escape('user:' . $this->authUser->id) . "' THEN 0 ELSE 1 END, shortname ASC");
253         } else {
254             $do->orderBy("shortname ASC");
255         }
256 // FIXME -> permissions on repositories goes here.. 
257         //$do->ensurePerm($this->authUser); 
258         //$do->fetchAll();
259         $do->find();
260         $this->repos= array();
261         while ($do->fetch()) {
262              
263             if (!$this->projectPerm($do->project_id, 'MTrack.Repos', 'S')) {
264                  continue;
265             }
266              
267             $this->repos[]= clone($do);
268             
269         }
270       
271     
272      
273     }
274     
275     
276
277
278 // static..?
279     function getBrowseData($repo, $pi, $object, $ident, $hashes = false)
280     {
281          $data = new StdClass;
282         $data->dirs = array();
283         $data->files = array();
284         $data->jumps = array();
285
286         if (!$repo) {
287             return $data;
288         }
289         $branches = $repo->getBranches();
290         $tags = $repo->getTags();
291         if (count($branches) + count($tags)) {
292             $jumps = array("" => "- Select Branch / Tag - ");
293             if (is_array($branches)) {
294                 foreach ($branches as $name => $notcare) {
295                     $jumps["branch:$name"] = "Branch: $name";
296                 }
297             }
298             if (is_array($tags)) {
299                 foreach ($tags as $name => $notcare) {
300                     $jumps["tag:$name"] = "Tag: $name";
301                 }
302             }
303             $data->jumps = $jumps;
304         }
305         $files = array();
306         $dirs = array();
307         
308         $needLog = array();
309         if ($repo) {
310             //try {
311             $ents = $repo->readdir($pi, $object, $ident);
312             if ($hashes === false) { 
313                 $ents = $this->cachedChangeEvent($ents);
314             } else {
315                 // we are filling the cache..
316                 return $this->cachedChangeEventFill($ents, $hashes);
317                  
318             }
319             
320             foreach ($ents as $file) {
321                 if (isset($file->hash)) {
322                     $needLog[] = $file->hash;
323                 }
324                 
325                 $basename = basename($file->name);
326                 if ($file->is_dir) {
327                     $dirs[$basename] = $file;
328                 } else {
329                     $files[$basename] = $file;
330                 }
331             }
332         }
333         uksort($files, 'strnatcmp');
334         uksort($dirs, 'strnatcmp');
335
336          
337         $data->dirs  = array_values($dirs);
338         $data->files = array_values($files);
339         
340         $this->repopath = $this->repo->displayName();
341         $this->needLog = $needLog;
342         // gather list 
343         
344         return $data;
345     }
346     /**
347      * New version of Directory listing
348      * Basically fetching the last change is very slow...
349      *  - 1st run through -> fetch all the change events that we have
350      *  for ones we do not have, fetch them later..
351      *
352      */
353     function cachedChangeEvent($ar)
354     {
355         // fetch a list of hashes...
356         // build a map...
357         $map = array();
358         foreach($ar as $e) {
359             $e->basename = basename($e->name);
360             // remove e->rev as it's not valid..
361             $e->rev = '....';
362             $map[$e->hash] = $e;
363             
364         }
365         
366         $q = DB_DataObject::factory('mtrack_clcache');
367         $q->whereAddIn('rev', array_keys($map), 'string');
368         $q->repo_id = $this->repo->id;
369         $revs = $q->fetchAll('rev', 'sobject');
370         $impl = $this->repo->impl();
371         
372         foreach($revs as $hash => $sobject) {
373             $event = $impl->commitLogToEvent($sobject);
374             // add something???
375             $event->is_dir = $map[$hash]->is_dir;
376             $event->name = $map[$hash]->name;
377             $event->basename = $map[$hash]->basename;
378             $map[$hash] =  $event; // this was previous only done for directories??? why???
379         }
380         return array_values($map);
381     }
382     
383     function cachedChangeEventFill($ar,$hashes)
384     {
385         $map = array();
386         $impl = $this->repo->impl();
387                 
388         foreach($ar as $e) {
389             if (!in_array($e->hash,$hashes)) {
390                 continue;
391             }
392             
393             // there is a small chance that concurrent users are looking for the same info..
394             $q = DB_DataObject::factory('mtrack_clcache');
395             $q->rev = $e->hash;
396             $q->repo_id = $this->repo->id;
397             
398             if ($q->find(true)) {
399                 $event = $impl->commitLogToEvent($q->sobject);
400             } else {}
401                 $ent = $this->repo->history($e->name, 1, 'rev', $e->rev);
402                 
403                 
404                 if (!$ent) {
405                     continue;
406                 }
407                 $event = $ent[0];
408                 // cache it..
409                 $q = DB_DataObject::factory('mtrack_clcache');
410                 $q->rev = $e->hash;
411                 $q->repo_id = $this->repo->id;
412                 $q->sobject = $event->commit;
413                 $q->insert();
414             }
415             
416             // only copy a few essentials from ent, as we will send it back via json.
417             // these all need escaoing..
418             $add = new stdClass;
419             $add->changelog = $event->changelogOneToHtml($this->link);
420             $add->age = $event->ctimeToHtml($this->link);
421             $add->basename = basename($e->name);
422             $add->changeby = htmlspecialchars($event->changeby); 
423             $add->rev = $event->rev; 
424             
425             $map[$e->hash] = $add;
426             
427             
428         }
429         return $map;
430         
431         
432     
433     }
434     function post($pi)
435     {
436         $this->pi =  $pi . (strlen($pi) ? $this->bootLoader->ext : '');
437        
438          
439          
440         $this->repo = DB_DataObject::factory('mtrack_repos');
441         $file = $this->repo->loadFromPath($this->pi);
442         if (!$this->repo->id) {
443             $this->jerr("INVALID URL");
444         }
445         
446         $this->object = null;
447         $this->ident = null;
448         
449         if (isset($_GET['jump']) && strlen($_GET['jump'])) {
450             list($this->object, $this->ident) = explode(':', $_GET['jump'], 2);
451         }
452         
453         if (!$this->repo) {
454             $this->jerr("INVALID URL");
455         }
456         if (!$this->projectPerm($this->repo->project_id, 'MTrack.Repos', 'S')) {
457             $this->jerr("INVALID URL");
458         }
459         if (empty($_POST['hashes'])) {
460             $this->jerr("INVALID URL");
461         }
462         
463         $ar =  $this->getBrowseData($this->repo, $file, $this->object, $this->ident, explode(',', $_POST['hashes']));
464         $this->jdata($ar);
465         exit;
466         
467         
468     }
469     
470     
471 }
472     
473      
474     
475      
476     
477
478