fetchAll(PDO::FETCH_COLUMN, 0); if (count($ids) == 1) { $cache[$id] = $ids[0]; } else { return null; } } return new MTrackIssue($cache[$id]); } static function registerListener(IMTrackIssueListener $l) // used by CustomFields { self::$_listeners[] = $l; } static function index_issue($object) { list($ignore, $ident) = explode(':', $object, 2); $i = MTrackIssue::loadById($ident); if (!$i) return; echo "Ticket #$i->nsident\n"; $CS = MTrackChangeset::get($i->updated); $CSC = MTrackChangeset::get($i->created); $kw = join(' ', array_values($i->getKeywords())); $idx = array( 'summary' => $i->summary, 'description' => $i->description, 'changelog' => $i->changelog, 'keyword' => $kw, 'stored:date' => $CS->when, 'who' => $CS->who, 'creator' => $CSC->who, 'stored:created' => $CSC->when, 'owner' => $i->owner ); $i->augmentIndexerFields($idx); MTrackSearchDB::add("ticket:$i->tid", $idx, true); foreach (MTrackDB::q('select value, changedate, who from change_audit left join changes using (cid) where fieldname = ?', "ticket:$ident:@comment") as $row) { list($text, $when, $who) = $row; $start = time(); $id = sha1($text); $elapsed = time() - $start; if ($elapsed > 4) { echo " - comment $who $when took $elapsed to hash\n"; } $start = time(); if (strlen($text) > 8192) { // A huge paste into a ticket $text = substr($text, 0, 8192); } MTrackSearchDB::add("ticket:$ident:comment:$id", array( 'description' => $text, 'stored:date' => $when, 'who' => $who, ), true); $elapsed = time() - $start; if ($elapsed > 4) { echo " - comment $who $when took $elapsed to index\n"; } } } //methods.. function __construct($tid = null) { if ($tid === null) { $this->components = array(); $this->origcomponents = array(); $this->milestones = array(); $this->origmilestones = array(); $this->keywords = array(); $this->origkeywords = array(); $this->status = 'new'; foreach (array('classification', 'severity', 'priority') as $f) { $this->$f = MTrackConfig::get('ticket', "default.$f"); } } else { $data = MTrackDB::q('select * from tickets where tid = ?', $tid)->fetchAll(); $row = null; if (isset($data[0])) { $row = $data[0]; } if (!is_array($row)) { throw new Exception("no such issue $tid"); } foreach ($row as $k => $v) { $this->$k = $v; } } } function applyPOSTData($data) { foreach (self::$_listeners as $l) { $l->applyPOSTData($this, $data); } } function augmentFormFields(&$FIELDSET) { foreach (self::$_listeners as $l) { $l->augmentFormFields($this, $FIELDSET); } } function augmentIndexerFields(&$idx) { foreach (self::$_listeners as $l) { $l->augmentIndexerFields($this, $idx); } } function augmentSaveParams(&$params) { foreach (self::$_listeners as $l) { $l->augmentSaveParams($this, $params); } } function checkVeto() { $args = func_get_args(); $method = array_shift($args); $veto = array(); foreach (self::$_listeners as $l) { $v = call_user_func_array(array($l, $method), $args); if ($v !== true) { $veto[] = $v; } } if (count($veto)) { $reasons = array(); foreach ($veto as $r) { if (is_array($r)) { foreach ($r as $m) { $reasons[] = $m; } } else { $reasons[] = $r; } } require_once 'Exception/Veto.php'; throw new MTrackVetoException($reasons); } } function save(MTrackChangeset $CS) { $db = MTrackDB::get(); $reindex = false; if ($this->tid === null) { $this->created = $CS->cid; $oldrow = array(); $reindex = true; } else { list($oldrow) = MTrackDB::q('select * from tickets where tid = ?', $this->tid)->fetchAll(); } $this->checkVeto('vetoSave', $this, $oldrow); $this->updated = $CS->cid; $params = array( 'summary' => $this->summary, 'description' => $this->description, 'created' => $this->created, 'updated' => $this->updated, 'owner' => $this->owner, 'changelog' => $this->changelog, 'priority' => $this->priority, 'severity' => $this->severity, 'classification' => $this->classification, 'resolution' => $this->resolution, 'status' => $this->status, 'estimated' => (float)$this->estimated, 'spent' => (float)$this->spent, 'nsident' => $this->nsident, 'cc' => $this->cc, ); $this->augmentSaveParams($params); if ($this->tid === null) { $sql = 'insert into tickets '; $keys = array(); $values = array(); require_once 'UUID.php'; $new_tid = new OmniTI_Util_UUID; $new_tid = $new_tid->toRFC4122String(false); $keys[] = "tid"; $values[] = "'$new_tid'"; foreach ($params as $key => $value) { $keys[] = $key; $values[] = ":$key"; } $sql .= "(" . join(', ', $keys) . ") values (" . join(', ', $values) . ")"; } else { $sql = 'update tickets set '; $values = array(); foreach ($params as $key => $value) { $values[] = "$key = :$key"; } $sql .= join(', ', $values) . " where tid = :tid"; $params['tid'] = $this->tid; } $q = $db->prepare($sql); $q->execute($params); if ($this->tid === null) { $this->tid = $new_tid; $created = true; } else { $created = false; } foreach ($params as $key => $value) { if ($key == 'created' || $key == 'updated' || $key == 'tid') { continue; } if ($key == 'changelog' || $key == 'description' || $key == 'summary') { if (!isset($oldrow[$key]) || $oldrow[$key] != $value) { $reindex = true; } } if (!isset($oldrow[$key])) { $oldrow[$key] = null; } $CS->add("ticket:$this->tid:$key", $oldrow[$key], $value); } $this->compute_diff($CS, 'components', 'ticket_components', 'compid', $this->components, $this->origcomponents); $this->compute_diff($CS, 'keywords', 'ticket_keywords', 'kid', $this->keywords, $this->origkeywords); $this->compute_diff($CS, 'milestones', 'ticket_milestones', 'mid', $this->milestones, $this->origmilestones); foreach ($this->comments_to_add as $text) { $CS->add("ticket:$this->tid:@comment", null, $text); } foreach ($this->effort as $effort) { MTrackDB::q('insert into effort (tid, cid, expended, remaining) values (?, ?, ?, ?)', $this->tid, $CS->cid, $effort[0], $effort[1]); } $this->effort = array(); } function getComponents() { if ($this->components !== null) { return $this->components ; } $q = MTrackDB::q(' select tc.compid, name from ticket_components tc left join components using (compid) where tid = ?', $this->tid); $this->origcomponents = array(); foreach ($q->fetchAll() as $row) { $this->origcomponents[$row[0]] = $row[1]; } $this->components = $this->origcomponents; return $this->components; } function assocComponent($comp) { $comp = $this->resolveComponent($comp); $this->getComponents(); $this->checkVeto('vetoComponent', $this, $comp, true); $this->components[$comp->compid] = $comp->name; } function dissocComponent($comp) { $comp = $this->resolveComponent($comp); $this->getComponents(); $this->checkVeto('vetoComponent', $this, $comp, false); unset($this->components[$comp->compid]); } function getMilestones() { if ($this->milestones === null) { $comps = MTrackDB::q('select tc.mid, name from ticket_milestones tc left join milestones using (mid) where tid = ? order by duedate, name', $this->tid)->fetchAll(); $this->origmilestones = array(); foreach ($comps as $row) { $this->origmilestones[$row[0]] = $row[1]; } $this->milestones = $this->origmilestones; } return $this->milestones; } function assocMilestone($M) { $ms = $this->resolveMilestone($M); if ($ms === null) { throw new Exception("unable to resolve milestone $M"); } $this->getMilestones(); $this->checkVeto('vetoMilestone', $this, $ms, true); $this->milestones[$ms->mid] = $ms->name; } function dissocMilestone($M) { $ms = $this->resolveMilestone($M); if ($ms === null) { throw new Exception("unable to resolve milestone $M"); } $this->getMilestones(); $this->checkVeto('vetoMilestone', $this, $ms, false); unset($this->milestones[$ms->mid]); } function addComment($comment) { $comment = trim($comment); if (strlen($comment)) { $this->checkVeto('vetoComment', $this, $comment); $this->comments_to_add[] = $comment; } } function assocKeyword($kw) { $kw = $this->resolveKeyword($kw); $this->getKeywords(); $this->checkVeto('vetoKeyword', $this, $kw, true); $this->keywords[$kw->kid] = $kw->keyword; } function dissocKeyword($kw) { $kw = $this->resolveKeyword($kw); $this->getKeywords(); $this->checkVeto('vetoKeyword', $this, $kw, false); unset($this->keywords[$kw->kid]); } function getKeywords() { if ($this->keywords === null) { $comps = MTrackDB::q('select tc.kid, keyword from ticket_keywords tc left join keywords using (kid) where tid = ?', $this->tid)->fetchAll(); $this->origkeywords = array(); foreach ($comps as $row) { $this->origkeywords[$row[0]] = $row[1]; } $this->keywords = $this->origkeywords; } return $this->keywords; } function addEffort($amount, $revised = null) { $diff = null; if ($revised !== null) { $diff = $revised - $this->estimated; $this->estimated = $revised; } $this->effort[] = array($amount, $diff); $this->spent += $amount; } function close() { $this->status = 'closed'; $this->addEffort(0, 0); } function isOpen() { switch ($this->status) { case 'closed': return false; default: return true; } } function reOpen() { $this->status = 'reopened'; $this->resolution = null; } function toArray() { $ret = get_object_vars($this); // echo '
'; print_r($ret);exit; return $ret; } /// rendering tricks function updateWho($link) { return $link->username( $this->updated ? MTrackChangeset::get($this->updated)->who : MTrackAuth::whoami() ); } function updateWhen($link) { return $link->date( $this->updated ? MTrackChangeset::get($this->updated)->when : MTrackDB::unixtime(time()) ); } function createdWho($link) { return $link->username( $this->created ? MTrackChangeset::get($this->created)->who : MTrackAuth::whoami() ); } function createdWhen($link) { return $link->date( $this->created ? MTrackChangeset::get($this->created)->when : MTrackDB::unixtime(time()) ); } function keywordsToHtml() { $value = array(); foreach ($this->getKeywords() as $kw) { $value[] = mtrack_keyword($kw); } return join(' ', $value); } function componentsToHtml() { $res = array(); $value = join(',',array_keys($this->getComponents())); if (!strlen($value)) { return ''; } $q = MTrackDB::q( "select name, deleted from components where compid in ($value)"); foreach ($q->fetchAll() as $row) { $c = ($row['deleted'] ? '' : '') . htmlentities($row['name'], ENT_QUOTES, 'utf-8') . ($row['deleted'] ? '' : ''); $res[] = $c; } return join(", ", $res); } function milestonesToHtml($msurl) { if (empty($this->milestone_url)) { die("requires issue->milestone_url to be set."); } $res = array(); $value = join(',',array_keys($this->getMilestones())); if (!strlen($value)) { return ''; } foreach (MTrackDB::q( "select name, completed, deleted from milestones where mid in ($value)") ->fetchAll() as $row) { $row['deleted'] = strlen($row['completed']) ? 1 : 0; $c = "milestone_url}" . urlencode($row['name']) . '">' . htmlentities($row['name'], ENT_QUOTES, 'utf-8') . ""; $res[] = $c; } return join(", ", $res); } function descriptionToHtml() { return MTrack_Wiki::format_to_html($this->description); } /* function attachmentsToHtml() { require_once 'Attachment.php'; return MTrackAttachment::renderList("ticket:{$this->tid}"); } function attachmentsDeleteToHtml() { require_once 'Attachment.php'; return MTrackAttachment::renderDeleteList("ticket:{$this->tid}"); } */ function toIdString() { return "ticket:{$this->tid}"; } // watcher related. function watcherButton() { return MTrackWatch::renderWatchUI('ticket', $this->tid); } function watchType() { return 'ticket'; } function watchId() { return $this->tid; } function watchers() { return MTrackWatch::objectWatchers($this); } private function resolveMilestone($ms) { if ($ms instanceof MTrack_Milestone) { return $ms; } if (ctype_digit($ms)) { return MTrack_Milestone::loadById($ms); } return MTrack_Milestone::loadByName($ms); } private function resolveKeyword($kw) { if ($kw instanceof MTrackKeyword) { return $kw; } $k = MTrackKeyword::loadByWord($kw); if ($k === null) { if (ctype_digit($kw)) { return MTrackKeyword::loadById($kw); } throw new Exception("unknown keyword $kw"); } return $k; } private function compute_diff(MTrackChangeset $CS, $label, $tablename, $keyname, $current, $orig) { if (!is_array($current)) { $current = array(); } if (!is_array($orig)) { $orig = array(); } $added = array_keys(array_diff_key($current, $orig)); $removed = array_keys(array_diff_key($orig, $current)); $db = MTrackDB::get(); $ADD = $db->prepare( "insert into $tablename (tid, $keyname) values (?, ?)"); $DEL = $db->prepare( "delete from $tablename where tid = ? AND $keyname = ?"); foreach ($added as $key) { $ADD->execute(array($this->tid, $key)); } foreach ($removed as $key) { $DEL->execute(array($this->tid, $key)); } if (count($added) + count($removed)) { $old = join(',', array_keys($orig)); $new = join(',', array_keys($current)); $CS->add( "ticket:$this->tid:@$label", $old, $new); } } private function resolveComponent($comp) { if ($comp instanceof MTrackComponent) { return $comp; } if (ctype_digit($comp)) { return MTrackComponent::loadById($comp); } return MTrackComponent::loadByName($comp); } }