sync
authorAlan Knowles <alan@akbkhome.com>
Wed, 23 Mar 2011 08:39:30 +0000 (16:39 +0800)
committerAlan Knowles <alan@akbkhome.com>
Wed, 23 Mar 2011 08:39:30 +0000 (16:39 +0800)
MTrackWeb.php [new file with mode: 0644]
MTrackWeb/Ticket.php [new file with mode: 0644]
MTrackWeb/templates/images/js/mtrack.browse.js [new file with mode: 0644]
MTrackWeb/templates/images/js/mtrack.ticket.js [new file with mode: 0644]
index.php [new file with mode: 0644]

diff --git a/MTrackWeb.php b/MTrackWeb.php
new file mode 100644 (file)
index 0000000..0c924b3
--- /dev/null
@@ -0,0 +1,425 @@
+<?php
+/**
+ * every class extends this...
+ */
+class MTrackWeb extends HTML_FlexyFramework_Page
+{
+    var $template = 'wiki.html';
+    var $priorities = array();
+    var $severities = array();
+    var $link = false; // the link handler..
+    
+    function hasPerm($what, $cando) {
+        // our whole perm logic sits in here....
+        
+        // here's how it works
+        // a) anonymous users - not authenticated.
+        // - can see projects that are in *PUBLIC project "MTrack.Repos", "S"
+        // - can see bugs that are in *PUBLIC project "MTrack.Issue", "S"
+        // - can see bugs that are in *PUBLIC project "MTrack.Wiki", "S"
+        if (!$this->authUser) {
+            if ($cando == 'S' &&
+                    in_array( $what , array( 'MTrack.Repos', 'MTrack.Issue', 'MTrack.Wiki'))) {
+                return true; // not a diffinative answer...
+            }
+            return false;
+        }
+        
+        return $this->authUser->hasPerm($what, $cando); 
+    }
+    
+    function projectPerm($project_id, $what, $cando)
+    {
+        if (!$project_id) {
+            return false;
+        }
+        $p = DB_DataObject::factory('Projects');
+        $p->get($project_id);
+        if (!$this->authUser) {
+            if ($p->code != '*PUBLIC') {
+                return false; // only public projects
+            }
+            if ($cando != 'S') {
+                return false;
+            }
+            // all permissions to view public stuff.
+            return true;
+        }
+        if (!$this->authUser->hasPerm($what, $cando)) {
+            echo "NO PERMS $what $cando";
+            echo '<PRE>'; print_r($this->authUser->getPerms());
+            return false;
+        }
+        // membership rules?
+        //echo "COMPTYPE " . $this->authUser->company()->comptype ;
+        if ($this->authUser->company()->comptype == 'OWNER') {
+                
+            if ($this->authUser->hasPerm('Core.Projects_All', $cando)) { // they can do what they like on all projects.
+               return true;
+            }
+           // return $p->hasPerm($what, $cando);
+        }
+        // otherwise they have to be a team member of that project.
+        
+        $pd = DB_DataObject::factory('ProjectDirectory');
+        $pd->project_id = $project_id;
+        $pd->user_id = $this->authUser->id;
+        if (!$pd->count()) {
+            return false;
+        }
+        return true;
+        
+        
+        
+        
+    }
+    
+    
+    function getAuthUser()
+    {
+        $u = DB_DataObject::factory('Person');
+        if (!$u->isAuth()) {
+            return false;
+        }
+        return $u->getAuthUser();
+    }
+    /**
+     * base getAuth allows everyone in..
+     */
+    
+    function getAuth()
+    {
+        $this->registerClasses(); // to be destroyed??
+        
+        $ff = HTML_FlexyFramework::get();
+        if ($ff->cli) {
+            return true;
+        }
+        
+        // default timezone first..
+        $ff = HTML_FlexyFramework::get();
+        if (isset($ff->MTrack['timezone'])) {
+            date_default_timezone_set($ff->MTrack['timezone']);
+        }
+        
+        //MTrackConfig::boot(); // eak.. .remove me...
+      
+        $this->authUser = DB_DataObject::factory('Person')->getAuthUser();
+        if (!$this->authUser) {
+            return true; // we do allow people in this far..
+        }
+        
+         
+        // timezone setting... -- this may be a good addon to our core person class.
+        
+        if (!empty($this->authUser->timezone)) {
+            date_default_timezone_set($this->authUser->timezone);
+            return;
+        }
+         
+        /// fixme...
+        //$this->authUser = 
+        return true; // anyone at present..
+    }
+    function get($loc='')
+    {
+        // 
+        if (!empty($loc)) {
+            die ("invalid location". htmlspecialchars($loc));
+        }
+        if (!$this->authUser) {
+             return HTML_FlexyFramework::run('Wiki'); 
+        }
+        return HTML_FlexyFramework::run('Wiki/Today'); 
+    }
+    function post()
+    {
+        header("Status: 404 Not Found");
+        die("not valid");
+    }
+    
+    
+    function initOptions()
+    {
+        
+         
+        $q = MTrackDB::q('select priorityname, value from priorities');
+
+        foreach ($q->fetchAll() as $row) {
+            $this->priorities[$row[0]] = $row[1];
+        }
+        $q = MTrackDB::q('select sevname, ordinal from severities');
+        
+        foreach ($q->fetchAll() as $row) {
+            $this->severities[$row[0]] = $row[1];
+        }
+
+    }
+    
+    function registerClasses()
+    {
+        require_once 'MTrack/Wiki.php';
+        require_once 'MTrack/Wiki/Item.php';
+        require_once 'MTrack/Milestone.php';
+        //require_once 'MTrack/Report.php';
+        //require_once 'MTrack/SearchDB.php';
+        //require_once 'MTrack/Watch.php'; 
+        //require_once 'MTrack/CommitChecker.php'; 
+      
+        
+        require_once 'MTrackWeb/LinkHandler.php';
+        require_once 'MTrack/Wiki/HTMLFormatter.php';
+        
+        $this->link = new MTrackWeb_LinkHandler();
+        MTrack_Wiki_HTMLFormatter::registerLinkHandler($this->link);
+
+        $r = DB_DataObject::factory('mtrack_repos');
+        $r->loadFromPath('default/wiki');
+        MTrack_Wiki_Item::$repo = $r->impl();
+        
+        
+        
+        //MTrack_Wiki::register_macro('MilestoneSummary', array('MTrack_Milestone', 'macro_MilestoneSummary'));
+       // MTrack_Wiki::register_macro('BurnDown', array('MTrack_Milestone', 'macro_BurnDown'));
+        //MTrack_Wiki::register_macro('RunReport', array('MTrack_Report', 'macro_RunReport')); << fixme how are we to hanlde this..
+        //MTrack_Wiki::register_macro('TicketQuery', array('MTrack_Report', 'macro_TicketQuery'));
+        MTrack_Wiki::register_macro('IncludeWikiPage', array('MTrack_Wiki', 'macro_IncludeWiki'));
+        MTrack_Wiki::register_macro('IncludeHelpPage', array('MTrack_Wiki', 'macro_IncludeHelp'));
+        MTrack_Wiki::register_macro('Comment', array('MTrack_Wiki', 'macro_comment'));
+        MTrack_Wiki::register_processor('comment', array('MTrack_Wiki', 'processor_comment'));
+        MTrack_Wiki::register_processor('html', array('MTrack_Wiki', 'processor_html'));
+        MTrack_Wiki::register_processor('dataset', array('MTrack_Wiki', 'processor_dataset'));
+
+
+        //MTrackACL::registerAncestry('milestone', 'Roadmap');
+        //MTrackACL::registerAncestry('report', 'Reports');
+        //MTrackACL::registerAncestry('snippet', 'Snippets');
+        //MTrackACL::registerAncestry('repo', 'Browser');
+        //MTrackACL::registerAncestry('enum',     'Enumerations');
+        //MTrackACL::registerAncestry("component", 'Components');
+        //MTrackACL::registerAncestry("project",  'Projects');
+        //MTrackACL::registerAncestry("ticket",   "Tickets");
+        //MTrackACL::registerAncestry('wiki', array('MTrack_Wiki_Item', '_get_parent_for_acl'));
+
+        //MTrackSearchDB::register_indexer('ticket', array('MTrackIssue', 'index_issue'));
+        //MTrackSearchDB::register_indexer('wiki', array('MTrack_Wiki_Item', 'index_item'));
+
+
+
+        //MTrackWatch::registerEventTypes('ticket', array( 'ticket' => 'Tickets' ));
+        //MTrackWatch::registerEventTypes('milestone', array( 'ticket' => 'Tickets', 'changeset' => 'Code changes' ));
+        //MTrackWatch::registerEventTypes('repo', array( 'ticket' => 'Tickets', 'changeset' => 'Code changes' ));
+
+        // should this get registered here??
+        //MTrackCommitChecker::addCheck('Wiki');
+        
+        
+        
+   }
+    
+    function favicon()
+    {
+        return false;
+        /// FIXME - we should allow upload of a favion...
+        $ff = HTML_FlexyFramework::get();
+        
+        
+    }
+     
+    
+    /* renders the attachment list for a given object */
+    // was Attachments::render
+    // move it to MTrackWebAttachemnt...
+    
+  function attachmentsToHtml($object)
+  {
+    return 'TODO';
+    if (is_object($object)) {
+        $object = $object->toIdString(); // eg. ticket:1
+    }
+    $atts = MTrackDB::q('
+      select * from attachments
+      left join changes on (attachments.cid = changes.cid)
+      where attachments.object = ? order by changedate, filename',
+        $object)->fetchAll(PDO::FETCH_ASSOC);
+
+    if (count($atts) == 0) return '';
+
+    $max_dim = 150;
+
+    $html = "<div class='attachment-list'><b>Attachments</b><ul>";
+    foreach ($atts as $row) {
+      $url = "{$this->baseURL}/Attachment/$object/". $row['cid'] . '/' . $row['filename'];
+      
+      $html .= "<li><a class='attachment'" .
+        " href='$url'>".
+        "$row[filename]</a> ($row[size]) added by " .
+        $this->link->username($row['who'], array(
+          'no_image' => true
+        )) .
+        " " . $this->link->date($row['changedate']);
+        require_once 'MTrack/Attachment.php';
+      list($width, $height) = getimagesize(MTrackAttachment::local_path($row['hash']));
+      if ($width + $height) {
+        /* limit maximum size */
+        if ($width > $max_dim) {
+          $height *= $max_dim / $width;
+          $width = $max_dim;
+        }
+        if ($height > $max_dim) {
+          $width *= $max_dim / $height;
+          $height = $max_dim;
+        }
+        $html .= "<br><a href='$url'><img src='$url' width='$width' border='0' height='$height'></a>";
+      }
+
+      $html .= "</li>\n";
+    }
+    $html .= "</ul></div>";
+    return $html;
+  }
+    function jerr($str, $errors=array()) // standard error reporting..
+    {
+        require_once 'Services/JSON.php';
+        $json = new Services_JSON();
+        
+        // log all errors!!!
+        //$this->addEvent("ERROR", false, $str);
+        /*
+        if (!empty($_REQUEST['returnHTML']) || 
+            (isset($_SERVER['CONTENT_TYPE']) && preg_match('#multipart/form-data#i', $_SERVER['CONTENT_TYPE']))
+        ) {
+            header('Content-type: text/html');
+            echo "<HTML><HEAD></HEAD><BODY>";
+            echo  $json->encodeUnsafe(array(
+                    'success'=> false, 
+                    'message' => $str, // compate with exeption / loadexception.
+
+                    'errors' => $errors ? $errors : true, // used by forms to flag errors.
+                    'authFailure' => !empty($errors['authFailure']),
+                ));
+            echo "</BODY></HTML>";
+            exit;
+        }
+        */
+        echo $json->encode(array(
+            'success'=> false, 
+            'data'=> array(), 
+            'message' => $str, // compate with exeption / loadexception.
+            'errors' => $errors ? $errors : true, // used by forms to flag errors.
+            'authFailure' => !empty($errors['authFailure']),
+        ));
+        exit;
+        
+    }
+    function jok($str)
+    {
+        
+        require_once 'Services/JSON.php';
+        $json = new Services_JSON();
+        /*
+        if (!empty($_REQUEST['returnHTML']) || 
+            (isset($_SERVER['CONTENT_TYPE']) && preg_match('#multipart/form-data#i', $_SERVER['CONTENT_TYPE']))
+        
+        ) {
+            header('Content-type: text/html');
+            echo "<HTML><HEAD></HEAD><BODY>";
+            echo  $json->encodeUnsafe(array('success'=> true, 'data' => $str));
+            echo "</BODY></HTML>";
+            exit;
+        }
+        */
+        
+        echo  $json->encode(array('success'=> true, 'data' => $str));
+        exit;
+        
+    }
+    /**
+     * output data for grids or tree
+     * @ar {Array} ar Array of data
+     * @total {Number|false} total number of records (or false to return count(ar)
+     * @extra {Array} extra key value list of data to pass as extra data.
+     * 
+     */
+    function jdata($ar,$total=false, $extra=array())
+    {
+        // should do mobile checking???
+        if ($total == false) {
+            $total = count($ar);
+        }
+        $extra=  $extra ? $extra : array();
+        require_once 'Services/JSON.php';
+        $json = new Services_JSON();
+        echo $json->encode(array('success' =>  true, 'total'=> $total, 'data' => $ar) + $extra);    
+        exit;
+        
+        
+    }
+    
+    /**
+     * ---------------- Logging ---------------   
+     */
+    
+    /**
+     * addEventOnce:
+     * Log an action (only if it has not been logged already.
+     * 
+     * @param {String} action  - group/name of event
+     * @param {DataObject|false} obj - dataobject action occured on.
+     * @param {String} any remarks 
+     */
+    
+    function addEventOnce($act, $obj = false, $remarks = '') 
+    {
+        $au = $this->getAuthUser();
+        $e = DB_DataObject::factory('Events');
+        $e->init($act,$obj,$remarks); 
+        if ($e->find(true)) {
+            return;
+        }
+        $this->addEvent($act, $obj, $remarks);
+    }
+    /**
+     * addEvent:
+     * Log an action.
+     * 
+     * @param {String} action  - group/name of event
+     * @param {DataObject|false} obj - dataobject action occured on.
+     * @param {String} any remarks 
+     */
+    
+    function addEvent($act, $obj = false, $remarks = '') 
+    {
+        $au = $this->getAuthUser();
+        $e = DB_DataObject::factory('Events');
+        $e->init($act,$obj,$remarks); 
+         
+        $e->event_when = date('Y-m-d H:i:s');
+        
+        $eid = $e->insert();
+        $ff  = HTML_FlexyFramework::get();
+        if (empty($ff->Pman['event_log_dir'])) {
+            return;
+        }
+        $file = $ff->Pman['event_log_dir']. date('/Y/m/d/'). $eid . ".php";
+        if (!file_exists(dirname($file))) {
+            mkdir(dirname($file),0700,true);
+        }
+        file_put_contents($file, var_export(array(
+            'REQUEST_URI' => empty($_SERVER['REQUEST_URI']) ? 'cli' : $_SERVER['REQUEST_URI'],
+            'GET' => empty($_GET) ? array() : $_GET,
+            'POST' => empty($_POST) ? array() : $_POST,
+        ), true));
+        
+        
+        
+    }
+
+    
+    
+
+}
\ No newline at end of file
diff --git a/MTrackWeb/Ticket.php b/MTrackWeb/Ticket.php
new file mode 100644 (file)
index 0000000..8db5858
--- /dev/null
@@ -0,0 +1,443 @@
+<?php # vim:ts=2:sw=2:et:
+/* For licensing and copyright terms, see the file named LICENSE */
+//require_once 'MTrack/Captcha.php';
+
+require_once 'MTrackWeb.php';
+class MTrackWeb_Ticket extends MTrackWeb 
+{
+    var $id; // 0 = new
+    var $issue;
+    var $preview;
+    var $error;
+    var $editable;
+    var $tid = 0; // or the MD5 rep.
+    
+     
+    
+     function getAuth() 
+    {
+        parent::getAuth();
+        //require_once 'MTrack/ACL.php';
+     //   MTrackACL::requireAllRights('Browser', 'read');
+        return true;
+  
+    }
+    
+    function get($pi= 0)
+    {
+    
+        if (!isset($_REQUEST['ajax_body'])) {
+            return;
+        }
+        $this->masterTemplate = 'ticket.html';
+        $this->id = $pi ?  $pi: (isset($_GET['id']) ? $_GET['id'] : 0);
+        $this->id  = $this->id  == 'new' ? 0 : $this->id;
+        $this->id  = (int) $this->id;
+        
+    
+        // -- load issue..
+
+        $this->issue = DB_DataObject::factory('mtrack_ticket');
+
+        if ($this->id) {
+            if (!$this->issue->get($this->id)) {
+                throw new Exception("Invalid ticket $this->id");
+            }
+        }
+        
+        
+        
+        
+        
+        //$this->issue->augmentFormFields($this->fieldset());
+
+
+        $this->preview = false;
+        $this->error = array();
+       
+       
+        if (!$this->id && !$this->hasPerm('MTrack.Issue','A')) {
+            return HTML_FlexyFramework::run('Noperm');
+        }
+        if ($this->id &&  (
+                !$this->hasPerm('MTrack.Issue','S') || // general permission to view
+                !$this->ticket->hasPerm($this->authUser,'S') // specific permission on this bug.
+                
+            )) {
+            return HTML_FlexyFramework::run('Noperm');
+        }
+        
+        // new is always editable..????
+        $this->editable = $this->id ?
+            $this->ticket->hasPerm($this->authUser,'E')  : true;
+         
+        $this->issue->milestoneURL = $this->baseURL.'/Milestone'; // fix me later..
+    
+        $this->showEditBar = false;
+         
+        if ($this->editable && $this->id   && !$this->preview) {
+            $this->showEditBar = true;
+        }
+         
+        $this->initEditForm();
+        
+      
+    }
+   
+    function post($pi=0) // handle the post...
+    {
+        die("TODO");
+       $this->get($pi);
+       
+        if (isset($_POST['cancel'])) {
+            header("Location: {$$this->baseURL}/Ticket/$this->issue->nsident");
+            exit;
+          }
+          
+          if (!MTrack_Captcha::check('ticket')) {
+            $this->error[] = "CAPTCHA failed, please try again";
+          }
+          $this->preview = isset($_POST['preview']) ? true : false;
+
+          $comment = '';
+          try {
+            if (!$this->id) {
+              MTrackACL::requireAllRights("Tickets", 'create');
+            } else {
+              MTrackACL::requireAllRights("ticket:" . $this->issue->tid, 'modify');
+            }
+          } catch (Exception $e) {
+            $this->error[] = $e->getMessage();
+          }
+          
+          if (!$this->id) {
+            $comment = empty($_POST['comment']) ? '' : $_POST['comment'];
+          }
+          
+          if (!strlen($comment)) {
+            $comment = $_POST['summary'];
+          }
+          try {
+            $CS = MTrackChangeset::begin("ticket:X", $comment);
+          } catch (Exception $e) {
+            $this->error[] = $e->getMessage();
+            $CS = null;
+          }
+          if (!$this->id) {
+            // compute next id number.
+            // We don't use auto-number, because we allow for importing multiple
+            // projects with their own ticket sequence.
+            // During "normal" user-driven operation, we do want plain old id numbers
+            // so we compute it here, under a transaction
+            $db = MTrackDB::get();
+            
+            
+            
+            switch($db->getAttribute(PDO::ATTR_DRIVER_NAME)) {
+                case 'pgsql':
+                    // Some versions of postgres don't like that we have "abc123" for
+                    // identifiers, so match on the bigest number nsident fields only
+                    $max = "select max(cast(nsident as integer)) + 1 from tickets where nsident ~ '^\\\\d+$'";
+                    break;
+                
+                case 'mysql':
+                    $max = "select max(cast(nsident as UNSIGNED)) + 1 from tickets";
+                    break;
+                
+                default:
+                    $max = 'select max(cast(nsident as integer)) + 1 from tickets';   
+                    break;
+            }
+            
+            
+             
+            list($this->issue->nsident) = MTrackDB::q($max)->fetchAll(PDO::FETCH_COLUMN, 0);
+            if ($this->issue->nsident === null) {
+              $this->issue->nsident = 1;
+            }
+          }
+
+          if (isset($_POST['action']) && !$this->preview) {
+            $act= explode('_', $_POST['action'] , 2);
+            //var_dump($act);exit;
+            switch ($act[0]) {
+              case 'leave':
+                break;
+              case 'reopen':
+                $this->issue->reOpen();
+                break;
+              case 'fixed':
+                $this->issue->resolution = 'fixed';
+                $this->issue->close();
+                $_POST['estimated'] = $this->issue->estimated;
+                break;
+                
+              
+              case 'accept':
+                // will be applied to the issue further down
+                $_POST['owner'] = MTrackAuth::whoami();
+                if ($this->issue->status == 'new') {
+                  $this->issue->status = 'open';
+                }
+                break;
+                
+                
+              case 'resolve':
+                //$this->issue->resolution = $_POST['resolution'];
+                $this->issue->resolution = $act[1];
+                $this->issue->close();
+                $_POST['estimated'] = $this->issue->estimated;
+                break;  
+                
+              case 'change':
+                $this->issue->status = $act[1];
+                break;
+            }
+          }
+
+          $fields = array(
+            'summary',
+            'description',
+            'classification',
+            'priority',
+            'severity',
+            'changelog',
+            'owner',
+            'cc',
+          );
+
+          $this->issue->applyPOSTData($_POST);
+
+         
+          
+          foreach ($fields as $fieldname) {
+            if (isset($_POST[$fieldname]) && strlen($_POST[$fieldname])) {
+              $this->issue->$fieldname = $_POST[$fieldname];
+            } else {
+              $this->issue->$fieldname = null;
+            }
+          }
+
+          $kw = $this->issue->getKeywords();
+          $kill = array_values($kw);
+          foreach (preg_split('/[ \t,]+/', $_POST['keywords']) as $w) {
+            if (!strlen($w)) {
+              continue;
+            }
+            $x = array_search($w, $kw);
+            if ($x === false) {
+              $k = MTrackKeyword::loadByWord($w);
+              if ($k === null) {
+                $k = new MTrackKeyword;
+                $k->keyword = $w;
+                $k->save($CS);
+              }
+              $this->issue->assocKeyword($k);
+            } else {
+              $w = array_search($w, $kill);
+              if ($w !== false) {
+                unset($kill[$w]);
+              }
+            }
+          }
+          foreach ($kill as $w) {
+            $this->issue->dissocKeyword($w);
+          }
+
+          $ms = $this->issue->getMilestones();
+          $kill = $ms;
+          if (isset($_POST['milestone']) && is_array($_POST['milestone'])) {
+            foreach ($_POST['milestone'] as $mid) {
+              $this->issue->assocMilestone($mid);
+              unset($kill[$mid]);
+            }
+          }
+          foreach ($kill as $mid) {
+            $this->issue->dissocMilestone($mid);
+          }
+
+          $ms = $this->issue->getComponents();
+          $kill = $ms;
+          if (isset($_POST['component']) && is_array($_POST['component'])) {
+            foreach ($_POST['component'] as $mid) {
+              $this->issue->assocComponent($mid);
+              unset($kill[$mid]);
+            }
+          }
+          foreach ($kill as $mid) {
+            $this->issue->dissocComponent($mid);
+          }
+          
+            if (!empty($_POST['comment'])) {
+               $this->issue->addComment($_POST['comment']);
+            }
+          
+          $this->issue->addEffort(
+            empty($_POST['spent']) ? 0 : $_POST['spent'], 
+            empty($_POST['estimate']) ? 0 : $_POST['estimate']
+        );
+
+          if (!count($this->error)) {
+            try {
+              $this->issue->save($CS);
+              
+              // make sure everyone is watching it!!!!
+                if($this->issue->owner && $this->issue->tid) {
+                  // make sure owner is tracking it...
+                    MTrackWatch::watch_object('ticket', $this->issue->tid,  $this->issue->owner);
+                }
+                
+                if ($this->id == 'new') {
+                    MTrackWatch::watch_object('ticket', $this->issue->tid,  MTrackAuth::whoami());
+                }
+              
+              
+              $CS->setObject("ticket:" . $this->issue->tid);
+            } catch (Exception $e) {
+              $this->error[] = $e->getMessage();
+            }
+        }
+
+        if (!count($this->error)) {
+            if (!empty($_FILES['attachments'])) {
+                require_once 'MTrack/Attachment.php';
+                foreach ($_FILES['attachments']['name'] as $fileid => $name) {
+                      
+                    MTrackAttachment::add("ticket:{$this->issue->tid}",
+                        $_FILES['attachments']['tmp_name'][$fileid],
+                        $_FILES['attachments']['name'][$fileid],
+                        $CS
+                    );
+                }
+            }
+        }
+        if (!count($this->error) && $this->id != 'new') {
+            require_once 'MTrack/Attachment.php';
+            MTrackAttachment::process_delete("ticket:{$this->issue->tid}", $CS);
+        }
+
+        if (isset($_POST['apply']) && !count($this->error)) {
+          $CS->commit();
+          header("Location: {$this->baseURL}/Ticket/{$this->issue->nsident}");
+          exit;
+        }
+    }
+      
+         
+    function initEditForm($params = array())
+    {
+        require_once 'HTML/Template/Flexy/Element.php';
+        require_once 'HTML/Template/Flexy/Factory.php';
+        $this->elements = array();
+        
+        
+        
+        foreach(array( 'classification', 'priority', 'severity', 'resolution' ) as $c)  {
+            $d = DB_DataObject::factory('core_enum');
+            $d->etype = $c;
+            $d->orderBy('seqid ASC, name ASC');
+            if (!$d->count()) {
+                $d->createBaseEntries();
+                
+            }
+            $this->elements[$c] = new HTML_Template_Flexy_Element('select');
+            $this->elements[$c]->setOptions($d->fetchAll('id','name'));
+            
+        }
+        
+        if ($this->issue->project_id) {
+            $d = DB_DataObject::factory('mtrack_project_component');
+            $d->project_id = $this->issue->project_id;
+            $d->orderBy('name');
+            $d->whereAdd('deleted != 1');
+            $this->elements['component[]'] = new HTML_Template_Flexy_Element('select');
+            $this->elements['component[]']->setOptions($d->fetchAll('id', 'name'));
+            $ar = $this->issue->components();
+            $this->elements['component[]']->setValue(array_keys($ar));
+        
+        }
+        if ($this->issue->project_id) {
+            $d = DB_DataObject::factory('mtrack_milestone');
+            $d->project_id = $this->issue->project_id;
+            $d->orderBy('(case when duedate is null then 1 else 0 end), duedate, name');
+            $d->whereAdd('completed != 1');
+            $d->whereAdd('deleted != 1');
+            $this->elements['milestone[]'] = new HTML_Template_Flexy_Element('select');
+            $this->elements['milestone[]']->setOptions($d->fetchAll('id', 'name'));
+            $ar = $this->issue->milestones();
+            $this->elements['milestone[]']->setValue(array_keys($ar));
+        }
+        
+        // FIX ME - need to determine who the owner is..
+        // for a new issue it's the person who created it.
+        // later on it's an assignement???
+        
+        $users = array();
+         
+        $this->elements['owner'] = new HTML_Template_Flexy_Element('select');
+        $this->elements['owner']->setOptions($users);
+        
+        
+        
+        // keywords -- in toArray...
+        // milestone 
+          
+          
+          $this->change_status = array();
+          $this->resolve_status = array();
+          if ($this->id) {
+         
+            // for coder's they can only change this ticke to certian states
+            
+            //print_r($groups);
+            // Nasty - I really do not like the acl code in this ...
+            require_once 'MTrack/TicketState.php';
+            
+            $ST = new MTrackTicketState;
+            $ST = $ST->enumerate();
+            //print_r($ST);
+            unset($ST['closed']);
+            unset($ST[$this->issue->status]);
+           
+            $this->change_status = empty($ST) ? array() : array_keys($ST);
+            
+            $ac = MTrackAuth::getUserClass($this->authUser->userid);
+            //var_dump($ac);exit;
+// KLUDGE! - remove later...
+            if ($ac == 'admin') {
+                
+                $this->resolve_status= array('fixed');
+                $R = new MTrackResolution;
+                $resolutions = $R->enumerate();
+                unset($resolutions['fixed']);
+                
+                $this->resolve_status= array_keys($resolutions);
+                array_unshift($this->resolve_status, 'fixed');
+               // $html .= $this->mtrack_chg_status('action', 'resolve', 'Resolve as:', 'resolution', $resolutions, $this->issue );
+            } 
+
+           // } else {
+           //   $html .= mtrack_radio('action', 'reopen', $_POST['action']);
+           //   $html .= " <label for='reopen'>Reopen ticket</label><br>\n";
+           // }
+           
+        }
+        $this->elements = HTML_Template_Flexy_Factory::fromArray($this->issue->toArray(), $this->elements);
+        if (!empty($_POST)) {
+            $this->elements = HTML_Template_Flexy_Factory::fromArray($_POST, $this->elements);
+        }
+        
+        
+
+          
+    }
+   
+     
+    function eq($a,$b) {
+        return $a == $b;
+    }
+    
+}
\ No newline at end of file
diff --git a/MTrackWeb/templates/images/js/mtrack.browse.js b/MTrackWeb/templates/images/js/mtrack.browse.js
new file mode 100644 (file)
index 0000000..3fcbbc0
--- /dev/null
@@ -0,0 +1,131 @@
+
+/**
+ * funky tree nav... - only available to the newest of browsers..
+ *
+ */
+try { 
+    window.onpopstate = function(ev) {
+         
+        if (!ev.state || typeof(ev.state.url) == 'undefined') {
+            return;
+        }
+        MTrack.ajaxLoad(ev.state.url, false);
+};
+} catch (e) {}
+    
+     
+MTrack.missingHashRequest = false;
+MTrack.missingHashes = function(el,url) {
+    //console.log('finding missing hashes?');
+    var hashes = [];
+    el.find('.browse-missing-hash').each(function() {
+        hashes.push(this.id);
+    });
+    if (!hashes.length) {
+        return;
+    }
+    if (MTrack.missingHashRequest) {
+        MTrack.missingHashRequest.abort();
+    }
+    MTrack.missingHashRequest = jQuery.ajax({
+        type : 'POST',
+        url : baseURL + url,
+        data : { hashes : hashes.join(',') },
+        success : function(data) {
+            //console.log(data.data);
+            if (data.data) {
+                for(var hash in data.data) {
+                    var o = data.data[hash];
+                    $('#'+ hash).html(o.changeby + ' : ' + o.changelog);
+                    $('#age-'+ hash).html(o.age); // do magic replacement!!!
+                    $('#rev-'+ hash).html(o.rev); // do magic replacement!!!
+                    MTrack.addHandlers($('#age-'+ hash));
+                    MTrack.missingHashRequest  = false;
+                }
+                
+            }
+           // console.log(data)
+        }
+    });
+    
+};
+    
+
+MTrack.ajaxLoad = function(url,slideleft) { 
+
+    var target = $("#ajaxbody");
+    var content = $("#content");
+    target.css('position', 'relative');
+    
+    $('.mask').show(); // must show first?
+    $('.mask-loading').show();
+    // content has a border...
+    var t = content.offset().top - $(document).scrollTop() - 13;
+    var l = content.offset().left - $(document).scrollLeft() - 13;
+    var w = content.width() + 26;
+    var h = content.height() + 26;
+    $('.mask').offset( { top: t,  left: l});
+    $('.mask').width( w );
+    $('.mask').height( h );
+    
+    $('.mask-loading').offset( {
+            top: t +   16,
+            left: l + ( w / 2) - 16
+    });
+    
+    if (MTrack.missingHashRequest) {
+        MTrack.missingHashRequest.abort();
+        MTrack.missingHashRequest = false;
+    }
+    $('#loader').hide();
+    jQuery.ajax( {
+        url : baseURL + url,
+        data : { ajax_body : 1 },
+        success : function(data) {
+            
+            $('.mask').hide();
+            $('#loader').html(data);
+            $('#loader').show();
+            $('#loader').width( w );
+            if (slideleft) { 
+                $('#loader').offset({ top: t  ,  left: l+w });
+            } else {
+                $('#loader').offset({ top: t  ,  left: l-w });
+            }
+            
+            target.animate( { left: slideleft ? -1 * w  : w }, 500, function() {
+                target.css( { left: 0, top: 0}); // reset it at end of animation - as it ends up with the new content.
+            });
+            
+            $('#loader').animate( { left: l+13 }, 500, function () {
+                target.html( $('#loader').html());
+                $('#loader').hide();
+                $('#loader').html('');
+                target.show();// make sure!!
+                
+                MTrack.missingHashes(target,  url);
+                MTrack.addHandlers(target);
+            });
+        }
+        //console.log('loaded dif');
+        
+           
+    });
+  
+    return false;  
+
+}
+
+
+MTrack.register('a.browse-link', 'click', function(event) 
+{
+    event.preventDefault();
+  
+    var href= this.getAttribute('href').substring(baseURL.length);
+    try { 
+        window.history.pushState( { url: href }, "Browse : " + href , this.href );
+    } catch (e) {}
+    var slideleft = $(this).is('.browse-link-up')  ? 0 : 1;    
+    MTrack.ajaxLoad(href,slideleft); 
+});
diff --git a/MTrackWeb/templates/images/js/mtrack.ticket.js b/MTrackWeb/templates/images/js/mtrack.ticket.js
new file mode 100644 (file)
index 0000000..c266588
--- /dev/null
@@ -0,0 +1,365 @@
+//<script type="text/javascript">
+/*
+
+ticket code..
+
+basically works like this.
+
+The edit page has a number of sections
+'viewblock'
+.mtrack-view-editbar << edit bar for view.
+*/
+MTrack.register('.mtrack-ticket-edit', 'each', function(el)
+{
+   
+});
+
+MTrack.register('.mtrack-ticket-events', 'each', function(el) {
+    // load events.  
+})
+
+MTrack.register('mtrack-ticket-desc', 'each', function(el) {
+    
+    
+});
+var formChanged = 0;
+var id;
+var issue_tid_null;
+var viewblock;
+var view_off;
+var view_pos;
+var editblock;
+var edit_off;
+var edit_pos;
+var commentblock;
+
+// from ticket.php
+
+
+function show_comment_form()
+{
+    
+    
+    // hide stuff first.
+    //viewblock.css('position', view_pos);
+    //viewblock.css('top', view_off.top);
+    $("#comment-submit-buttons").hide();
+    $(".mtrack-make-comment").hide();
+    viewblock.hide();
+    
+    // show stuff next.
+    
+    $("#update-issue-desc").show()
+    $("#comment-container").show();
+    // what's this about...
+    $("#edit-comment-parent").append($("#comment-area"));
+    $("#comment-area").show();
+     
+    
+    
+    
+    editblock.show();
+    //edit_off = editblock.offset();
+    //edit_pos = editblock.css('position');
+    $("#description").focus();
+
+    
+    //compute_floats();
+}
+function show_edit_form()
+{
+    //view_off = viewblock.offset();
+    //view_pos = viewblock.css('position');
+    //
+    //if (view_off) {
+    //    viewblock.css('position', view_pos);
+    //    viewblock.css('top', view_off.top);
+    //}
+
+    $("#issue-desc").hide();
+    $("#comment-submit-buttons").hide();
+    $(".mtrack-make-comment").hide();
+    $("#comment-container").hide();
+    viewblock.hide();
+    
+    $("#update-issue-desc").show()
+     $("#edit-issue-desc").show();
+    //$("#edit-comment-parent").append($("#comment-area"));
+    
+    //$("#comment-area").show();
+    
+    $("#description").focus();
+
+    editblock.show();
+    //edit_off = editblock.offset();
+    //edit_pos = editblock.css('position');
+    
+    
+    
+    //compute_floats();
+}
+function cancel_form_changes()
+{
+    if (formChanged) {
+        document.location.href = document.location.href;
+        return false;
+    }
+
+    //editblock.css('position', edit_pos);
+    //editblock.css('top', edit_off.top);
+    editblock.hide();
+    viewblock.show();
+
+    $("#tktedit").each(function(){
+        // reset form
+        this.reset();
+    });
+    // notify asm select of change
+    $("select[multiple]").change();
+    $("#edit-issue-desc").hide();
+    $("#update-issue-desc").hide()
+    $("#original-comment-parent").append($("#comment-area"));
+    $("#comment-submit-buttons").show();
+
+    if (id != 'new') { 
+        $("#comment-area").hide();
+        $(".mtrack-make-comment").show();
+    } 
+    $("#issue-desc").show();
+    formChanged = false;
+    //compute_floats();
+
+    return false;
+}
+/*
+
+function compute_floats() // disabled at present..
+{
+   return;
+   if ($(viewblock).is(':visible')) {
+      view_off = viewblock.offset();
+    if ($(this).scrollTop() > view_off.top) {
+      viewblock.css('position', 'fixed');
+      viewblock.css('top', '0px');
+      viewblock.addClass('button-float-floating');
+    } else {
+      viewblock.css('position', view_pos);
+      viewblock.css('top', view_off.top);
+      viewblock.removeClass('button-float-floating');
+    }
+  }
+  if ($(editblock).is(':visible')) {
+    edit_off = editblock.offset();
+    if ($(this).scrollTop() > edit_off.top) {
+      editblock.css('position', 'fixed');
+      editblock.css('top', '0px');
+      editblock.addClass('button-float-floating');
+    } else if ($(this).scrollTop() < edit_off.top + editblock.height() - $(this).height()) {
+      editblock.css('position', 'fixed');
+      editblock.css('top', $(this).height() - editblock.outerHeight());
+      editblock.addClass('button-float-floating');
+    } else {
+      editblock.css('position', edit_pos);
+      editblock.css('top', edit_off.top);
+      editblock.removeClass('button-float-floating');
+    }
+  }
+}
+*/
+
+var mtrack_ticked_loaded = false;
+$(document).ready(function()
+{
+    
+    if (mtrack_ticked_loaded) {
+        return;
+    }
+    mtrack_ticked_loaded = true;
+    
+    viewblock = $('#tkt-view-button-block');
+    editblock = $('#tkt-edit-button-block');
+    
+    if (typeof(tid) == 'undefined') {
+        return; // not a ticket page..
+       }
+    
+    
+    
+    if (!tid){
+        show_edit_form();
+        $('#tkt-edit-button-block .mtrack-edit-cancel').hide();
+    }   
+   // $(window).scroll(function () {
+   //   compute_floats();
+   // });
+
+    
+    if (tid) {
+        // load the events..
+        $('#events-list').load( baseURL + '/Events/' + tid, function() {
+            $('abbr.timeinterval').timeago();
+            $('.ticketchangeinfo').hide();
+            
+            $('.ticketevent-expand').click(function() {
+                var n = this.id.split('-').pop();
+                var target = "[id='ticketchangeinfo-" + n + "']";
+                var loadit = $(target).is(":hidden") && !$(target)[0].getAttribute('loaded');
+                 
+                 
+                $(target).toggle(100);
+                
+                
+                if (loadit && n.match(/\./)) {
+                    
+                    $(target)[0].innerHTML = '&#160;Loading...';
+        
+                    $(target)[0].setAttribute('loaded', 'true');
+                    $(target).load(  baseURL + '/Changeset/' + this.title + '?nowrap=1');
+                }
+                
+                
+            })
+            
+        }) ;
+        
+        // load the watchers..
+        new mtrack.watch({
+            objname : 'ticket',
+            objid : tid,
+            domid : 'watch-list'
+        });
+         
+    }
+    
+    $(".mtrack-edit-desc").click(
+      function() {
+        show_edit_form();
+        return false;
+      }
+    );
+    
+    
+    $("input[type=radio]").click(
+      function() {
+        if (this.value == 'fixed') {
+          $("#changelog-container").show();
+        } else {
+          $("#changelog-container").hide();
+        }
+      }
+    );
+
+    $(":input").change(function() {
+        formChanged = true;
+    });
+    
+    
+    $("textarea").keyup(function() {
+      // This is here because IE doesn't seem to reliably trigger the
+      // change event with textareas
+        formChanged = true;
+    });
+    
+    // dialogs..
+    
+    $("#confirmCancelDialog").dialog({
+          autoOpen: false,
+          bgiframe: true,
+          resizable: false,
+          modal: true,
+          buttons: {
+            'Discard': function() {
+              $(this).dialog('close');
+              cancel_form_changes();
+            },
+            'Keep': function() {
+              $(this).dialog('close');
+            }
+          }
+    });
+    
+    $("#noCommentDialog").dialog({
+          autoOpen: false,
+          bgiframe: true,
+          resizable: false,
+          modal: true,
+          buttons: {
+            'OK': function() {
+              $(this).dialog('close');
+              $("#comment").focus();
+            }
+          }
+    });
+    $("#noSummaryDialog").dialog({
+          autoOpen: false,
+          bgiframe: true,
+          resizable: false,
+          modal: true,
+          buttons: {
+            'OK': function() {
+              $(this).dialog('close');
+              $("#summary").focus();
+            }
+          }
+    });
+
+
+    $(".mtrack-edit-cancel").click(
+          function() {
+            if (formChanged) {
+              $("#confirmCancelDialog").dialog('open');
+              return false;
+            } else {
+              return cancel_form_changes();
+            }
+          }
+    );
+    
+    
+    $(".mtrack-make-comment").click(
+          function() {
+            show_comment_form();
+            $("#comment").focus();
+            return false;
+          }
+    );
+
+    $(".mtrack-button-submit").click(function(){
+
+         
+        if ($("#summary").val() == '') {
+
+            $("#summary").addClass('error');
+            $("#noSummaryDialog").dialog('open');
+            return false;
+
+        } else {
+
+            if (formChanged == false && $("#comment").val() == '') {
+                $("#comment").addClass('error');
+                $("#noCommentDialog").dialog('open');
+                return false;
+            }
+
+        }
+
+    });
+
+    $("#comment").keydown(function(){
+        $("#comment").removeClass('error');
+    });
+
+    $("#summary").keydown(function(){
+        $("#summary").removeClass('error');
+    });
+
+    if (issue_tid_null) {
+        $("#summary").focus();
+
+    }
+    $('#button-show-overflow').click(function() {
+          $('#show-overflow').hide('blind');
+          $('#ticketcommentsoverflow').show('clip');
+          return false;
+        });
+});
diff --git a/index.php b/index.php
new file mode 100644 (file)
index 0000000..455daf3
--- /dev/null
+++ b/index.php
@@ -0,0 +1,48 @@
+<?php
+
+// this will be generate eventually by the 'setup.php' script..
+
+
+ini_set('include_path', 
+    dirname(__FILE__) . PATH_SEPARATOR .
+    dirname(__FILE__) . '/pear');
+
+require_once 'HTML/FlexyFramework.php';
+
+define('DB_DATAOBJECT_NO_OVERLOAD',true);
+
+ini_set('display_errors', true);
+
+new HTML_FlexyFramework(array(
+       
+    'project' => 'MTrackWeb',
+    'enable' => 'MTrack,Core,Admin', // enable permissions from Pman 
+    'database'        => 'mysql://root:@localhost/mtrack',
+    'DB_DataObject' => array(
+        
+
+        'class_location' => implode(PATH_SEPARATOR, array(
+                dirname(__FILE__). '/Pman/MTrack/DataObjects',
+                dirname(__FILE__). '/Pman/Core/DataObjects',
+                
+        )),
+        'class_prefix' => 'Pman_MTrack_DataObjects_:Pman_Core_DataObjects_:',
+        
+        'ini_mtrack' => implode(PATH_SEPARATOR, array(
+                dirname(__FILE__). '/PmanMTrack/DataObjects/pman.ini',
+                dirname(__FILE__). '/Pman/Core/DataObjects/pman.ini',
+                
+        )),
+    ),
+    'MTrackWeb' => array(
+        'working_dir' => '/var/lib/mtrack', // this needs to be writable by web user..
+    )
+    
+));
+    
+             
+
+
+