all urls need to respond to direct requests. * -> the direct request is always an ajax * * * */ // Browse.php - only for rendering the body.. // Tree.php - the actually tree.. require_once 'MTrackWeb.php'; class MTrackWeb_Browse extends MTrackWeb { var $object; var $ident; var $repo; var $elements = array(); var $showCreate = false; // show create repo. var $canEditRepo = false; var $canFork = false; var $canDeleteFork = false; var $forkname = ''; var $bdata = false; var $up = ''; var $jump = ''; var $pi; var $basename; var $crumbs; var $dirname; var $repos; function getAuth() { parent::getAuth(); return true; } function get($pi='', $args = array()) { $this->pi = $pi . (strlen($pi) ? $this->bootLoader->ext : ''); if (!isset($_REQUEST['ajax_body'])) { $this->title = "Browse: " . $this->pi; return; } $this->masterTemplate = 'tree.html'; //DB_DataObject::debugLevel(1); //var_dump($pi); $this->repo = DB_DataObject::factory('mtrack_repos'); $file = $this->repo->loadFromPath($this->pi); if (!$this->repo->id) { $this->repo = false; } // if we have an active project.. enforce it.. if ($this->currentProject() && $this->repo && $this->repo->project_id != $this->currentProject()) { $this->repo = false; // no repo.. } $this->object = null; $this->ident = null; if (isset($_GET['jump']) && strlen($_GET['jump'])) { list($this->object, $this->ident) = explode(':', $_GET['jump'], 2); } if ($this->repo) { if (!$this->projectPerm($this->repo->project_id, 'MTrack.Repos', 'S')) { return HTML_FlexyFramework::run('Noperm'); // noperm = loggedin -> need more perms / not.. try loggin in.. } $this->bdata = $this->getBrowseData($this->repo, $file, $this->object, $this->ident); if (isset($this->bdata->err) ) { throw new Exception($this->bdata->err); } require_once 'MTrack/Wiki/Item.php'; //make sure we can render.. MTrack_Wiki_Item::$repo = $this->repo->impl(); } //$this->repo = (count($crumbs) > 2) ? MTrackSCM::factory($this->pi) : null ; //$this->bdata = mtrack_cache( // array($this,'getBrowseData'), // array($this->repo, $this->pi, $this->object, $this->ident) //); $this->basename = basename($file); $location = ''; $crumbs = !strlen($file) || dirname($file) == '.' || !strlen(dirname($file)) ? array() : explode('/', dirname($file)); $this->crumbs = array(); if (!$this->repo) { $crumbs = array(); } foreach($crumbs as $path) { $c = new StdClass; $c->name = $path ; $location .= strlen($location) ? '/' : ''; $location .= strlen($path) ? urlencode($path) : ''; $c->location = $location; $this->crumbs[] = $c; } // echo '
';print_R($this->crumbs);
         
        if ($this->bdata && count($this->bdata->jumps)) {
            require_once 'HTML/Template/Flexy/Element.php';
            $this->elements['jump'] = new HTML_Template_Flexy_Element('select');
           // print_r($bdata->jumps);
            $this->elements['jump']->setOptions($this->bdata->jumps);
        }
        
        
        
        
         /*
        if (MTrackACL::hasAllRights('Browser', 'create')) {
          some users may have rights to create repos that belong to projects.
            * Determine that list of projects here, because we need it for both
            * the fork and new repo cases */
            /*
            $owners = array("user:{$this->authUser->userid}" => $this->authUser->userid);
            $q = MTrackDB::q( 'select projid, shortname, name from projects order by ordinal') ;
            foreach ($q->fetchAll(PDO::FETCH_ASSOC) as $row) {
                if (MTrackACL::hasAllRights("project:". $row['projid'], 'modify')) {
                    $owners["project:". $row['shortname']] = $row['shortname'];
                }
            }
            $this->showCreate = count($owners) > 1 ? true : false;
            require_once 'HTML/Template/Flexy/Element.php';
            $this->elements['repo:parent'] = new HTML_Template_Flexy_Element('select');
            $this->elements['repo:parent']->setOptions($owners);
            
        }
          
        if ($this->repo) {
            MTrackACL::requireAllRights("repo:{$this->repo->id}", 'read');
             
            // this looks buggy..
           
            if (    $this->repo->canFork() && 
                    MTrackACL::hasAllRights('Browser', 'fork') &&
                    MTrackConfig::get('repos', 'allow_user_repo_creation')
                ) {
                    
                $this->canFork = true;
                $this->forkname = $this->repo->shortname;
                
                if ("{$this->authUser->userid}/{$this->repo->shortname}" == $this->repo->getBrowseRootName()) {
                    // if this is mine already, make a "more unique" name for my new fork 
                    $this->forkname = $this->repo->shortname . '2';
                }
            }
            
            if (    $this->repo->parent &&
                    MTrackACL::hasAllRights("repo:{$this->repo->id}", "delete")
                ) {
                    
                $this->canDeleteFork = true;
            }
            
            if (MTrackACL::hasAllRights("repo:{$this->repo->id}", "modify")) {
                $this->canEditRepo = true;
            }
            // watch UI is different in this version..
            // MTrackWatch::renderWatchUI('repo', $repo->id);
            
        }
         */
        /// non repo options..
        

        
        $this->dirname = $this->repo ? $this->repo->displayName() : '';
        // up...
        if (strlen($file)) {
            $this->dirname .= '/'. $file;
        }
        $this->up =  strlen($file) ? dirname($this->dirname) : '';
        $this->jump =  isset($_GET['jump']) ? $_GET['jump'] : '';
    
         if (!$this->repo) {
            return $this->noRepo(explode('/', $pi));
         
        }
        //print_r($this); exit;
        
          
    }
     
    function noRepo($crumbs)
    {
        
        /*if (  MTrackACL::hasAllRights('Browser', 'fork')
            && MTrackConfig::get('repos', 'allow_user_repo_creation')
            ){
                
            $repotypes = array();
            
            foreach (MTrack_Repo::getAvailableSCMs() as $t => $r) {
              $d = $r->getSCMMetaData();
              $repotypes[$t] = $d['name'];
            }
            require_once 'HTML/Template/Flexy/Element.php';
            $this->elements['repo:type'] = new HTML_Template_Flexy_Element('select');
            $this->elements['repo:type']->setOptions($repotypes);
        }
        */
       // DB_DataObject::debugLevel(1);
        $mine = $this->authUser ? "user:{$this->authUser->id}" : '';
        
        $do = DB_DataObject::factory('mtrack_repos');
        $params = array();
         
        $crumbs[0]  = empty($crumbs[0]) ? 'default' : $crumbs[0] ;
        
        if (count($crumbs) == 1 && $crumbs[0] != 'default') {
        
             $do->whereAdd("parent like('%:". $do->escape($crumbs[0]). "')"); // subset
             
         } else if (count($crumbs) == 1 && $crumbs[0] == 'default') {
         
            $do->whereAdd(" parent = ''"); // top level..
         } else {
             /* looking for top level items */
             ///$where = "1 = 1";
         }
         /* have my own repos bubble up */
        if ($this->authUser) {
            $do->orderBy("CASE WHEN  parent = '" . $do->escape('user:' . $this->authUser->id) . "' THEN 0 ELSE 1 END, shortname ASC");
        } else {
            $do->orderBy("shortname ASC");
        }
        
        if ($this->currentProject()) {
            $do->project_id = $this->currentProject();
        }
// FIXME -> permissions on repositories goes here.. 
        //$do->ensurePerm($this->authUser); 
        //$do->fetchAll();
        $do->find();
        $this->repos= array();
        while ($do->fetch()) {
             
            if (!$this->projectPerm($do->project_id, 'MTrack.Repos', 'S')) {
                 continue;
            }
             
            $this->repos[]= clone($do);
            
        }
      
    
     
    }
    
    


// static..?
    function getBrowseData($repo, $pi, $object, $ident, $hashes = false)
    {
         $data = new StdClass;
        $data->dirs = array();
        $data->files = array();
        $data->jumps = array();

        if (!$repo) {
            return $data;
        }
        $branches = $repo->getBranches();
        $tags = $repo->getTags();
        if (count($branches) + count($tags)) {
            $jumps = array("" => "- Select Branch / Tag - ");
            if (is_array($branches)) {
                foreach ($branches as $name => $notcare) {
                    $jumps["branch:$name"] = "Branch: $name";
                }
            }
            if (is_array($tags)) {
                foreach ($tags as $name => $notcare) {
                    $jumps["tag:$name"] = "Tag: $name";
                }
            }
            $data->jumps = $jumps;
        }
        $files = array();
        $dirs = array();
        
        $needLog = array();
        if ($repo) {
            //try {
            $ents = $repo->readdir($pi, $object, $ident);
            if ($hashes === false) { 
                $ents = $this->cachedChangeEvent($ents);
            } else {
                // we are filling the cache..
                return $this->cachedChangeEventFill($ents, $hashes);
                 
            }
            
            foreach ($ents as $file) {
                if (isset($file->hash)) {
                    $needLog[] = $file->hash;
                }
                
                $basename = basename($file->name);
                if ($file->is_dir) {
                    $dirs[$basename] = $file;
                } else {
                    $files[$basename] = $file;
                }
            }
        }
        uksort($files, 'strnatcmp');
        uksort($dirs, 'strnatcmp');

         
        $data->dirs  = array_values($dirs);
        $data->files = array_values($files);
        
        $this->repopath = $this->repo->displayName();
        $this->needLog = $needLog;
        // gather list 
        
        return $data;
    }
    /**
     * New version of Directory listing
     * Basically fetching the last change is very slow...
     *  - 1st run through -> fetch all the change events that we have
     *  for ones we do not have, fetch them later..
     *
     */
    function cachedChangeEvent($ar)
    {
        // fetch a list of hashes...
        // build a map...
        $map = array();
        foreach($ar as $e) {
            $e->basename = basename($e->name);
            // remove e->rev as it's not valid..
            $e->rev = false;
            $map[$e->hash] = $e;
            
        }
        
        $q = DB_DataObject::factory('mtrack_clcache');
        $q->whereAddIn('rev', array_keys($map), 'string');
        $q->repo_id = $this->repo->id;
        $revs = $q->fetchAll('rev', 'sobject');
        $impl = $this->repo->impl();
        
        foreach($revs as $hash => $sobject) {
            $event = $impl->commitLogToEvent($sobject);
            // add something??? 
            if (!$event) {
                continue;
            }
            $event->is_dir = $map[$hash]->is_dir;
            $event->name = $map[$hash]->name;
            $event->basename = $map[$hash]->basename;
        
            $map[$hash] =  $event; // this was previous only done for directories??? why???
        }
        return array_values($map);
    }
    
    function cachedChangeEventFill($ar,$hashes)
    {
        $map = array();
        $impl = $this->repo->impl();
                
        foreach($ar as $e) {
            if (!in_array($e->hash,$hashes)) {
                continue;
            }
            
            // there is a small chance that concurrent users are looking for the same info..
            $q = DB_DataObject::factory('mtrack_clcache');
            $q->rev = $e->hash;
            $q->repo_id = $this->repo->id;
            
            if ($q->find(true)) {
                $event = $impl->commitLogToEvent($q->sobject);
            } else { 
                $ent = $this->repo->history($e->name, 1, 'rev', $e->rev);
                
                
                if (!$ent) {
                    continue;
                }
                $event = $ent[0];
                // cache it..
                $q = DB_DataObject::factory('mtrack_clcache');
                $q->rev = $e->hash;
                $q->repo_id = $this->repo->id;
                $q->sobject = $event->commit;
                $q->insert();
            }
            
            // only copy a few essentials from ent, as we will send it back via json.
            // these all need escaoing..
            $add = new stdClass;
            $add->changelog = $event->changelogOneToHtml($this->link);
            $add->age = $event->ctimeToHtml($this->link);
            $add->basename = basename($e->name);
            $add->changeby = htmlspecialchars($event->changeby); 
            $add->rev = ''.
                        htmlspecialchars($event->rev) .
                        '';
            
            $map[$e->hash] = $add;
            
            
        }
        return $map;
        
        
    
    }
    function post($pi)
    {
        $this->pi =  $pi . (strlen($pi) ? $this->bootLoader->ext : '');
       
         
         
        $this->repo = DB_DataObject::factory('mtrack_repos');
        $file = $this->repo->loadFromPath($this->pi);
        if (!$this->repo->id) {
            $this->jerr("INVALID URL");
        }
        
        $this->object = null;
        $this->ident = null;
        
        if (isset($_GET['jump']) && strlen($_GET['jump'])) {
            list($this->object, $this->ident) = explode(':', $_GET['jump'], 2);
        }
        
        if (!$this->repo) {
            $this->jerr("INVALID URL");
        }
        if (!$this->projectPerm($this->repo->project_id, 'MTrack.Repos', 'S')) {
            $this->jerr("INVALID URL");
        }
        if (empty($_POST['hashes'])) {
            $this->jerr("INVALID URL");
        }
        
        $ar =  $this->getBrowseData($this->repo, $file, $this->object, $this->ident, explode(',', $_POST['hashes']));
        $this->jdata($ar);
        exit;
        
        
    }
    
    
}