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