php8
[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         // 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..
74         }
75         
76         
77         $this->object = null;
78         $this->ident = null;
79         
80         if (isset($_GET['jump']) && strlen($_GET['jump'])) {
81             list($this->object, $this->ident) = explode(':', $_GET['jump'], 2);
82         }
83         
84         if ($this->repo) {
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..
87             }
88             
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);
92             }
93             require_once 'MTrack/Wiki/Item.php';
94             //make sure we can render..
95             MTrack_Wiki_Item::$repo = $this->repo->impl();
96         }    
97         //$this->repo  = (count($crumbs) > 2) ?   MTrackSCM::factory($this->pi) : null ;
98          
99         
100         
101         
102        
103         
104         //$this->bdata = mtrack_cache(
105         //    array($this,'getBrowseData'), 
106         //    array($this->repo, $this->pi, $this->object, $this->ident)
107         //);
108
109         $this->basename  = basename($file);
110         $location = '';
111         $crumbs = !strlen($file) || dirname($file) == '.' || !strlen(dirname($file)) ? array() :
112             explode('/', dirname($file));
113         
114         
115         $this->crumbs = array();
116         
117         if (!$this->repo) {
118             $crumbs = array();
119         }
120         
121         foreach($crumbs as $path) 
122         {
123              
124             $c = new StdClass;
125             $c->name =   $path ;
126             $location .=  strlen($location) ? '/' : '';
127             $location .=  strlen($path)  ?  urlencode($path)  : '';
128             $c->location = $location;
129             $this->crumbs[] = $c;
130         }
131        // echo '<PRE>';print_R($this->crumbs);
132          
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);
138         }
139         
140         
141         
142         
143          /*
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 */
148             /*
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'];
154                 }
155             }
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);
160             
161         }
162           
163         if ($this->repo) {
164             MTrackACL::requireAllRights("repo:{$this->repo->id}", 'read');
165              
166             // this looks buggy..
167            
168             if (    $this->repo->canFork() && 
169                     MTrackACL::hasAllRights('Browser', 'fork') &&
170                     MTrackConfig::get('repos', 'allow_user_repo_creation')
171                 ) {
172                     
173                 $this->canFork = true;
174                 $this->forkname = $this->repo->shortname;
175                 
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';
179                 }
180             }
181             
182             if (    $this->repo->parent &&
183                     MTrackACL::hasAllRights("repo:{$this->repo->id}", "delete")
184                 ) {
185                     
186                 $this->canDeleteFork = true;
187             }
188             
189             if (MTrackACL::hasAllRights("repo:{$this->repo->id}", "modify")) {
190                 $this->canEditRepo = true;
191             }
192             // watch UI is different in this version..
193             // MTrackWatch::renderWatchUI('repo', $repo->id);
194             
195         }
196          */
197         /// non repo options..
198         
199
200         
201         $this->dirname = $this->repo ? $this->repo->displayName() : '';
202         // up...
203         if (strlen($file)) {
204             $this->dirname .= '/'. $file;
205         }
206         $this->up =  strlen($file) ? dirname($this->dirname) : '';
207         $this->jump =  isset($_GET['jump']) ? $_GET['jump'] : '';
208     
209          if (!$this->repo) {
210             return $this->noRepo(explode('/', $pi));
211          
212         }
213         //print_r($this); exit;
214         
215           
216     }
217      
218     function noRepo($crumbs)
219     {
220         
221         /*if (  MTrackACL::hasAllRights('Browser', 'fork')
222             && MTrackConfig::get('repos', 'allow_user_repo_creation')
223             ){
224                 
225             $repotypes = array();
226             
227             foreach (MTrack_Repo::getAvailableSCMs() as $t => $r) {
228               $d = $r->getSCMMetaData();
229               $repotypes[$t] = $d['name'];
230             }
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);
234         }
235         */
236        // DB_DataObject::debugLevel(1);
237         $mine = $this->authUser ? "user:{$this->authUser->id}" : '';
238         
239         $do = DB_DataObject::factory('mtrack_repos');
240         $params = array();
241          
242         $crumbs[0]  = empty($crumbs[0]) ? 'default' : $crumbs[0] ;
243         
244         if (count($crumbs) == 1 && $crumbs[0] != 'default') {
245         
246              $do->whereAdd("parent like('%:". $do->escape($crumbs[0]). "')"); // subset
247              
248          } else if (count($crumbs) == 1 && $crumbs[0] == 'default') {
249          
250             $do->whereAdd(" parent = ''"); // top level..
251          } else {
252              /* looking for top level items */
253              ///$where = "1 = 1";
254          }
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");
258         } else {
259             $do->orderBy("shortname ASC");
260         }
261         
262         if ($this->currentProject()) {
263             $do->project_id = $this->currentProject();
264         }
265 // FIXME -> permissions on repositories goes here.. 
266         //$do->ensurePerm($this->authUser); 
267         //$do->fetchAll();
268         $do->find();
269         $this->repos= array();
270         while ($do->fetch()) {
271              
272             if (!$this->projectPerm($do->project_id, 'MTrack.Repos', 'S')) {
273                  continue;
274             }
275              
276             $this->repos[]= clone($do);
277             
278         }
279       
280     
281      
282     }
283     
284     
285
286
287 // static..?
288     function getBrowseData($repo, $pi, $object, $ident, $hashes = false)
289     {
290          $data = new StdClass;
291         $data->dirs = array();
292         $data->files = array();
293         $data->jumps = array();
294
295         if (!$repo) {
296             return $data;
297         }
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";
305                 }
306             }
307             if (is_array($tags)) {
308                 foreach ($tags as $name => $notcare) {
309                     $jumps["tag:$name"] = "Tag: $name";
310                 }
311             }
312             $data->jumps = $jumps;
313         }
314         $files = array();
315         $dirs = array();
316         
317         $needLog = array();
318         if ($repo) {
319             //try {
320             $ents = $repo->readdir($pi, $object, $ident);
321             if ($hashes === false) { 
322                 $ents = $this->cachedChangeEvent($ents);
323             } else {
324                 // we are filling the cache..
325                 return $this->cachedChangeEventFill($ents, $hashes);
326                  
327             }
328             
329             foreach ($ents as $file) {
330                 if (isset($file->hash)) {
331                     $needLog[] = $file->hash;
332                 }
333                 
334                 $basename = basename($file->name);
335                 if ($file->is_dir) {
336                     $dirs[$basename] = $file;
337                 } else {
338                     $files[$basename] = $file;
339                 }
340             }
341         }
342         uksort($files, 'strnatcmp');
343         uksort($dirs, 'strnatcmp');
344
345          
346         $data->dirs  = array_values($dirs);
347         $data->files = array_values($files);
348         
349         $this->repopath = $this->repo->displayName();
350         $this->needLog = $needLog;
351         // gather list 
352         
353         return $data;
354     }
355     /**
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..
360      *
361      */
362     function cachedChangeEvent($ar)
363     {
364         // fetch a list of hashes...
365         // build a map...
366         $map = array();
367         foreach($ar as $e) {
368             $e->basename = basename($e->name);
369             // remove e->rev as it's not valid..
370             $e->rev = false;
371             $map[$e->hash] = $e;
372             
373         }
374         
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();
380         
381         foreach($revs as $hash => $sobject) {
382             $event = $impl->commitLogToEvent($sobject);
383             // add something??? 
384             if (!$event) {
385                 continue;
386             }
387             $event->is_dir = $map[$hash]->is_dir;
388             $event->name = $map[$hash]->name;
389             $event->basename = $map[$hash]->basename;
390         
391             $map[$hash] =  $event; // this was previous only done for directories??? why???
392         }
393         return array_values($map);
394     }
395     
396     function cachedChangeEventFill($ar,$hashes)
397     {
398         $map = array();
399         $impl = $this->repo->impl();
400                 
401         foreach($ar as $e) {
402             if (!in_array($e->hash,$hashes)) {
403                 continue;
404             }
405             
406             // there is a small chance that concurrent users are looking for the same info..
407             $q = DB_DataObject::factory('mtrack_clcache');
408             $q->rev = $e->hash;
409             $q->repo_id = $this->repo->id;
410             
411             if ($q->find(true)) {
412                 $event = $impl->commitLogToEvent($q->sobject);
413             } else { 
414                 $ent = $this->repo->history($e->name, 1, 'rev', $e->rev);
415                 
416                 
417                 if (!$ent) {
418                     continue;
419                 }
420                 $event = $ent[0];
421                 // cache it..
422                 $q = DB_DataObject::factory('mtrack_clcache');
423                 $q->rev = $e->hash;
424                 $q->repo_id = $this->repo->id;
425                 $q->sobject = $event->commit;
426                 $q->insert();
427             }
428             
429             // only copy a few essentials from ent, as we will send it back via json.
430             // these all need escaoing..
431             $add = new stdClass;
432             $add->changelog = $event->changelogOneToHtml($this->link);
433             $add->age = $event->ctimeToHtml($this->link);
434             $add->basename = basename($e->name);
435             $add->changeby = htmlspecialchars($event->changeby); 
436             $add->rev = '<a class="changesetlink browse-link"  href="'. 
437                         htmlspecialchars($this->baseURL) .
438                         '/Changeset/' .
439                         htmlspecialchars($this->repo->displayName()). 
440                         '/'.
441                         htmlspecialchars($event->rev) .
442                         '">'.
443                         htmlspecialchars($event->rev) .
444                         '</a>';
445             
446             $map[$e->hash] = $add;
447             
448             
449         }
450         return $map;
451         
452         
453     
454     }
455     function post($pi)
456     {
457         $this->pi =  $pi . (strlen($pi) ? $this->bootLoader->ext : '');
458        
459          
460          
461         $this->repo = DB_DataObject::factory('mtrack_repos');
462         $file = $this->repo->loadFromPath($this->pi);
463         if (!$this->repo->id) {
464             $this->jerr("INVALID URL");
465         }
466         
467         $this->object = null;
468         $this->ident = null;
469         
470         if (isset($_GET['jump']) && strlen($_GET['jump'])) {
471             list($this->object, $this->ident) = explode(':', $_GET['jump'], 2);
472         }
473         
474         if (!$this->repo) {
475             $this->jerr("INVALID URL");
476         }
477         if (!$this->projectPerm($this->repo->project_id, 'MTrack.Repos', 'S')) {
478             $this->jerr("INVALID URL");
479         }
480         if (empty($_POST['hashes'])) {
481             $this->jerr("INVALID URL");
482         }
483         
484         $ar =  $this->getBrowseData($this->repo, $file, $this->object, $this->ident, explode(',', $_POST['hashes']));
485         $this->jdata($ar);
486         exit;
487         
488         
489     }
490     
491     
492 }
493     
494      
495     
496      
497     
498
499