protected function _getIssueFromRequest(TBGRequest $request) { if ($issue_no = TBGContext::getRequest()->getParameter('issue_no')) { $issue = TBGIssue::getIssueFromLink($issue_no); if ($issue instanceof TBGIssue) { if (!$this->selected_project instanceof TBGProject || $issue->getProjectID() != $this->selected_project->getID()) { $issue = null; } } else { TBGLogging::log("Issue no [{$issue_no}] not a valid issue no", 'main', TBGLogging::LEVEL_WARNING_RISK); } } TBGLogging::log('done (Loading issue)'); //$this->getResponse()->setPage('viewissue'); if ($issue instanceof TBGIssue && (!$issue->hasAccess() || $issue->isDeleted())) { $issue = null; } return $issue; }
public function extractIssues($matches) { $issue = TBGIssue::getIssueFromLink($matches["issues"]); if ($issue instanceof TBGIssue) { if (!TBGContext::isProjectContext() || TBGContext::isProjectContext() && $issue->getProjectID() == TBGContext::getCurrentProject()->getID()) { $this->foundissues[$issue->getID()] = $issue; $this->resultcount++; } } }
public function runUpdateIssueDetails(TBGRequest $request) { $this->forward403if(TBGContext::getCurrentProject()->isArchived()); $this->error = false; try { $i18n = TBGContext::getI18n(); $issue = TBGIssue::getIssueFromLink($request['issue_no']); if ($issue->getProject()->getID() != $this->selected_project->getID()) { throw new Exception($i18n->__('This issue is not valid for this project')); } if (!$issue instanceof TBGIssue) { die; } $workflow_transition = null; if ($passed_transition = $request['workflow_transition']) { //echo "looking for transition "; $key = str_replace(' ', '', mb_strtolower($passed_transition)); //echo $key . "\n"; foreach ($issue->getAvailableWorkflowTransitions() as $transition) { //echo str_replace(' ', '', mb_strtolower($transition->getName())) . "?"; if (mb_strpos(str_replace(' ', '', mb_strtolower($transition->getName())), $key) !== false) { $workflow_transition = $transition; //echo "found transition " . $transition->getID(); break; } //echo "no"; } if (!$workflow_transition instanceof TBGWorkflowTransition) { throw new Exception("This transition ({$key}) is not valid"); } } $fields = $request->getRawParameter('fields', array()); $return_values = array(); if ($workflow_transition instanceof TBGWorkflowTransition) { foreach ($fields as $field_key => $field_value) { $classname = "TBG" . ucfirst($field_key); $method = "set" . ucfirst($field_key); $choices = $classname::getAll(); $found = false; foreach ($choices as $choice_key => $choice) { if (mb_strpos(str_replace(' ', '', mb_strtolower($choice->getName())), str_replace(' ', '', mb_strtolower($field_value))) !== false) { $request->setParameter($field_key . '_id', $choice->getId()); break; } } } $request->setParameter('comment_body', $request['message']); $return_values['applied_transition'] = $workflow_transition->getName(); if ($workflow_transition->validateFromRequest($request)) { $retval = $workflow_transition->transitionIssueToOutgoingStepFromRequest($issue, $request); $return_values['transition_ok'] = $retval === false ? false : true; } else { $return_values['transition_ok'] = false; $return_values['message'] = "Please pass all information required for this transition"; } } elseif ($issue->isUpdateable()) { foreach ($fields as $field_key => $field_value) { try { if (in_array($field_key, array_merge(array('title', 'state'), TBGDatatype::getAvailableFields(true)))) { switch ($field_key) { case 'state': $issue->setState($field_value == 'open' ? TBGIssue::STATE_OPEN : TBGIssue::STATE_CLOSED); break; case 'title': if ($field_value != '') { $issue->setTitle($field_value); } else { throw new Exception($i18n->__('Invalid title')); } break; case 'description': case 'reproduction_steps': $method = "set" . ucfirst($field_key); $issue->{$method}($field_value); break; case 'status': case 'resolution': case 'reproducability': case 'priority': case 'severity': case 'category': $classname = "TBG" . ucfirst($field_key); $method = "set" . ucfirst($field_key); $choices = $classname::getAll(); $found = false; foreach ($choices as $choice_key => $choice) { if (str_replace(' ', '', mb_strtolower($choice->getName())) == str_replace(' ', '', mb_strtolower($field_value))) { $issue->{$method}($choice); $found = true; } } if (!$found) { throw new Exception('Could not find this value'); } break; case 'percent_complete': $issue->setPercentCompleted($field_value); break; case 'owner': case 'assignee': $set_method = "set" . ucfirst($field_key); $unset_method = "un{$set_method}"; switch (mb_strtolower($field_value)) { case 'me': $issue->{$set_method}(TBGContext::getUser()); break; case 'none': $issue->{$unset_method}(); break; default: try { $user = TBGUser::findUser(mb_strtolower($field_value)); if ($user instanceof TBGUser) { $issue->{$set_method}($user); } } catch (Exception $e) { throw new Exception('No such user found'); } break; } break; case 'estimated_time': case 'spent_time': $set_method = "set" . ucfirst(str_replace('_', '', $field_key)); $issue->{$set_method}($field_value); break; case 'milestone': $found = false; foreach ($this->selected_project->getMilestones() as $milestone) { if (str_replace(' ', '', mb_strtolower($milestone->getName())) == str_replace(' ', '', mb_strtolower($field_value))) { $issue->setMilestone($milestone->getID()); $found = true; } } if (!$found) { throw new Exception('Could not find this milestone'); } break; default: throw new Exception($i18n->__('Invalid field')); } } $return_values[$field_key] = array('success' => true); } catch (Exception $e) { $return_values[$field_key] = array('success' => false, 'error' => $e->getMessage()); } } } if (!$workflow_transition instanceof TBGWorkflowTransition) { $issue->getWorkflow()->moveIssueToMatchingWorkflowStep($issue); } if (!array_key_exists('transition_ok', $return_values) || $return_values['transition_ok']) { $comment = new TBGComment(); $comment->setTitle(''); $comment->setContent($request->getParameter('message', null, false)); $comment->setPostedBy(TBGContext::getUser()->getID()); $comment->setTargetID($issue->getID()); $comment->setTargetType(TBGComment::TYPE_ISSUE); $comment->setModuleName('core'); $comment->setIsPublic(true); $comment->setSystemComment(false); $comment->save(); $issue->setSaveComment($comment); $issue->save(); } $this->return_values = $return_values; } catch (Exception $e) { //$this->getResponse()->setHttpStatus(400); return $this->renderJSON(array('failed' => true, 'error' => $e->getMessage())); } }
protected function _parse_issuelink($matches) { $theIssue = TBGIssue::getIssueFromLink($matches[0]); $output = ''; $classname = ''; if ($theIssue instanceof TBGIssue && ($theIssue->isClosed() || $theIssue->isDeleted())) { $classname = 'closed'; } if ($theIssue instanceof TBGIssue) { $output = ' ' . link_tag(make_url('viewissue', array('issue_no' => $theIssue->getFormattedIssueNo(false), 'project_key' => $theIssue->getProject()->getKey())), $theIssue->getFormattedTitle(), array('class' => $classname)); } else { $output = $matches[1]; } return $output; }
public function addNewCommit($project, $commit_msg, $old_rev, $new_rev, $date = null, $changed, $author) { /* Find issues to update */ $fixes_grep = "#((bug|issue|ticket|fix|fixes|fixed|fixing|applies to|closes|references|ref|addresses|re|see|according to|also see)\\s\\#?(([A-Z0-9]+\\-)?\\d+))#ie"; $output = ''; $f_issues = array(); try { TBGContext::getI18n(); } catch (Exception $e) { TBGContext::reinitializei18n(); } try { $project = new TBGProject($project); } catch (Exception $e) { return TBGContext::getI18n()->__('Error: Invalid project ID'); } if (preg_match_all($fixes_grep, $commit_msg, $f_issues)) { // Github if (is_array($changed)) { $entries = $changed; $changed = ''; // Now handle changed files foreach ($entries[0] as $file) { $changed .= 'M' . $file . "\n"; } // Now handle new files foreach ($entries[1] as $file) { $changed .= 'A' . $file . "\n"; } // Now handle deleted files foreach ($entries[2] as $file) { $changed .= 'D' . $file . "\n"; } } $f_issues = array_unique($f_issues[3]); $file_lines = preg_split('/[\\n\\r]+/', $changed); $files = array(); foreach ($file_lines as $aline) { $action = substr($aline, 0, 1); if ($action == "A" || $action == "U" || $action == "D" || $action == "M") { $theline = trim(substr($aline, 1)); $files[] = array($action, $theline); } } foreach ($f_issues as $issue_no) { TBGContext::setCurrentProject($project); $theIssue = TBGIssue::getIssueFromLink($issue_no, true); if ($theIssue instanceof TBGIssue) { $uid = 0; /* * Some VCSes use a different format of storing the committer's name. Systems like bzr, git and hg use the format * Joe Bloggs <*****@*****.**>, instead of a classic username. Therefore a user will be found via 4 queries: * a) First we extract the email if there is one, and find a user with that email * b) If one is not found - or if no email was specified, then instead test against the real name (using the name part if there was an email) * c) the username or full name is checked against the friendly name field * d) and if we still havent found one, then we check against the username * e) and if we STILL havent found one, we just say the user is id 0 (unknown user). */ if (preg_match("/(?<=<)(.*)(?=>)/", $author, $matches)) { $email = $matches[0]; // a) $crit = new B2DBCriteria(); $crit->setFromTable(TBGUsersTable::getTable()); $crit->addSelectionColumn(TBGUsersTable::ID); $crit->addWhere(TBGUsersTable::EMAIL, $email); $row = TBGUsersTable::getTable()->doSelectOne($crit); if ($row != null) { $uid = $row->get(TBGUsersTable::ID); } else { // Not found by email preg_match("/(?<=^)(.*)(?= <)/", $author, $matches); $author = $matches[0]; } } // b) if ($uid == 0) { $crit = new B2DBCriteria(); $crit->setFromTable(TBGUsersTable::getTable()); $crit->addSelectionColumn(TBGUsersTable::ID); $crit->addWhere(TBGUsersTable::REALNAME, $author); $row = TBGUsersTable::getTable()->doSelectOne($crit); if ($row != null) { $uid = $row->get(TBGUsersTable::ID); } } // c) if ($uid == 0) { $crit = new B2DBCriteria(); $crit->setFromTable(TBGUsersTable::getTable()); $crit->addSelectionColumn(TBGUsersTable::ID); $crit->addWhere(TBGUsersTable::BUDDYNAME, $author); $row = TBGUsersTable::getTable()->doSelectOne($crit); if ($row != null) { $uid = $row->get(TBGUsersTable::ID); } } // d) if ($uid == 0) { $crit = new B2DBCriteria(); $crit->setFromTable(TBGUsersTable::getTable()); $crit->addSelectionColumn(TBGUsersTable::ID); $crit->addWhere(TBGUsersTable::UNAME, $author); $row = TBGUsersTable::getTable()->doSelectOne($crit); if ($row != null) { $uid = $row->get(TBGUsersTable::ID); } } $theIssue->addSystemComment(TBGContext::getI18n()->__('Issue updated from code repository'), TBGContext::getI18n()->__('This issue has been updated with the latest changes from the code repository.<source>%commit_msg%</source>', array('%commit_msg%' => $commit_msg)), $uid); foreach ($files as $afile) { if ($date == null) { $date = time(); } TBGVCSIntegrationTable::addEntry($theIssue->getID(), $afile[0], $commit_msg, $afile[1], $new_rev, $old_rev, $uid, $date); } $output .= 'Updated ' . $theIssue->getFormattedIssueNo() . "\n"; } else { $output .= 'Can\'t find ' . $issue_no . ' so not updating that one.' . "\n"; } } } return $output; }
/** * Runs one or more regular expressions against a supplied text, extracts * issue numbers from it, and then obtains corresponding issues. The * function will also obtain information about transitions (if this was * specified in the text). This data can be used for transitioning the * issues through a workflow. * * Once the function finishes processing, it will return an array of format: * * array('issues' => tbg_issues, 'transitions' => transitions). * * tbgissues is an array consisting of TBGIssue instances. * * transitions is an array containing transition arrays. The transition * arrays are accessed with issue numbers as keys (e.g. 'PREFIX-1', * 'PREFIX-5' or '2', '3' etc). Each transition array has the following * format: * * array(0 => command, 1 => parameters) * * command is a string representing the transision command (for example * 'Resolve issue') from the workflow definition. parameters is an array * that contains parameters and their values that should be passed to the * transition step: * * array( 'PARAM1' => 'VALUE1', 'PARAM2' => 'VALUE2', ...) * * * @param text Text that should be parsed for issue numbers and transitions. * * @param preg An array of regular expressions that should be used for * matching issue numbers. If an empty array is provided (default), regular * expressions are obtained through TBGTextParser::getIssueRegex() call. The * regular expressions should contain two named parameters - 'issues' and * 'transitions'. These two will be used for extracting the issue number and * transition information. * * @return An array with two elements, one denoting the matched issues, one * denoting the transitions for issues. These elements can be accessed using * keys 'issues', and 'transitions'. The key 'issues' can be used for * accessing an array made-up of TBGIssue instances. The key 'transitions' * can be used for accessing an array containing transition information * about each issue. The 'transitions' array uses issue numbers as keys, * and contains ordered transition information (see above for detailed * description of format). */ public static function getIssuesFromTextByRegex($text, $preg = array()) { // Fetch the default regular expressions if required. if (!$preg) { $issue_match_regexes = TBGTextParser::getIssueRegex(); } $issue_numbers = array(); // Issue numbers $issues = array(); // Issue objects $transitions = array(); // Transition information // Iterate over all regular expressions that should be used for // issue/transition matching in commit message. foreach ($issue_match_regexes as $issue_match_regex) { $matched_issue_data = array(); // All data from regexp // If any match is found using the current regular expression, extract // the information. if (preg_match_all($issue_match_regex, $text, $matched_issue_data)) { // Identified issues are kept inside of named regex group. foreach ($matched_issue_data["issues"] as $key => $issue_number) { // Get the matched transitions for the issue. $matched_issue_transitions = $matched_issue_data["transitions"][$key]; // Create an empty array to store transitions for an issue. Don't // overwrite it. Use issue number as key for transitions. if (!array_key_exists($issue_number, $transitions)) { $transitions[$issue_number] = array(); } // Add the transition information (if any) for an issue. if ($matched_issue_transitions) { // Parse the transition information. Each transition string is in // format: // 'TRANSITION1: PARAM1_1=VALUE1_1 PARAM1_2=VALUE1_2; TRANSITION2: PARAM2_1=VALUE2_1 PARAM2_2=VALUE2_2' foreach (explode("; ", $matched_issue_transitions) as $transition) { // Split command from its parameters. $transition_data = explode(": ", $transition); $transition_command = $transition_data[0]; // Set-up array that will contain parameters $transition_parameters = array(); // Process parameters if they were present. if (count($transition_data) == 2) { // Split into induvidual parameters. foreach (explode(" ", $transition_data[1]) as $parameter) { // Only process proper parameters (of format 'PARAM=VALUE') if (mb_strpos($parameter, '=')) { list($param_key, $param_value) = explode('=', $parameter); $transition_parameters[$param_key] = $param_value; } } } // Append the transition information for the current issue number. $transitions[$issue_number][] = array($transition_command, $transition_parameters); } } // Add the issue number to the list. $issue_numbers[] = $issue_number; } } } // Make sure that each issue gets procssed only once for a single commit // (avoid duplication of commits). $issue_numbers = array_unique($issue_numbers); // Fetch all issues affected by the commit. foreach ($issue_numbers as $issue_no) { $issue = TBGIssue::getIssueFromLink($issue_no); if ($issue instanceof TBGIssue) { $issues[] = $issue; } } // Return array consisting out of two arrays - one with TBGIssue // instances, and the second one with transition information for those // issues. return array("issues" => $issues, "transitions" => $transitions); }
/** * View an issue * * @param TBGRequest $request */ public function runViewIssue(TBGRequest $request) { //TBGEvent::listen('core', 'viewissue', array($this, 'listenViewIssuePostError')); TBGLogging::log('Loading issue'); if ($issue_no = TBGContext::getRequest()->getParameter('issue_no')) { $issue = TBGIssue::getIssueFromLink($issue_no); if ($issue instanceof TBGIssue) { if (!$this->selected_project instanceof TBGProject || $issue->getProjectID() != $this->selected_project->getID()) { $issue = null; } } else { TBGLogging::log("Issue no [{$issue_no}] not a valid issue no", 'main', TBGLogging::LEVEL_WARNING_RISK); } } TBGLogging::log('done (Loading issue)'); //$this->getResponse()->setPage('viewissue'); if ($issue instanceof TBGIssue && (!$issue->hasAccess() || $issue->isDeleted())) { $issue = null; } if ($issue instanceof TBGIssue) { if (!array_key_exists('viewissue_list', $_SESSION)) { $_SESSION['viewissue_list'] = array(); } $k = array_search($issue->getID(), $_SESSION['viewissue_list']); if ($k !== false) { unset($_SESSION['viewissue_list'][$k]); } array_push($_SESSION['viewissue_list'], $issue->getID()); if (count($_SESSION['viewissue_list']) > 10) { array_shift($_SESSION['viewissue_list']); } TBGEvent::createNew('core', 'viewissue', $issue)->trigger(); } $message = TBGContext::getMessageAndClear('issue_saved'); $uploaded = TBGContext::getMessageAndClear('issue_file_uploaded'); if ($request->isMethod(TBGRequest::POST) && $issue instanceof TBGIssue && $request->hasParameter('issue_action')) { switch ($request->getParameter('issue_action')) { case 'save': if ($issue->hasUnsavedChanges()) { if (!$issue->hasMergeErrors()) { try { $issue->getWorkflowStep()->getWorkflow()->moveIssueToMatchingWorkflowStep($issue); $issue->save(); TBGContext::setMessage('issue_saved', true); $this->forward(TBGContext::getRouting()->generate('viewissue', array('project_key' => $issue->getProject()->getKey(), 'issue_no' => $issue->getFormattedIssueNo()))); } catch (TBGWorkflowException $e) { $this->error = $e->getMessage(); $this->workflow_error = true; } catch (Exception $e) { $this->error = $e->getMessage(); } } else { $this->issue_unsaved = true; } } else { $this->forward(TBGContext::getRouting()->generate('viewissue', array('project_key' => $issue->getProject()->getKey(), 'issue_no' => $issue->getFormattedIssueNo()))); } break; } } elseif ($message == true) { $this->issue_saved = true; } elseif ($uploaded == true) { $this->issue_file_uploaded = true; } elseif (TBGContext::hasMessage('issue_error')) { $this->error = TBGContext::getMessageAndClear('issue_error'); } elseif (TBGContext::hasMessage('issue_message')) { $this->issue_message = TBGContext::getMessageAndClear('issue_message'); } $issuelist = array(); $issues = TBGContext::getUser()->getStarredIssues(); if (count($issues)) { foreach ($issues as $starred_issue) { if (!$starred_issue instanceof TBGIssue || !$starred_issue->getProject() instanceof TBGProject || !$this->selected_project instanceof TBGProject) { continue; } if ($starred_issue->isOpen() && $starred_issue->getProject()->getID() == $this->selected_project->getID()) { $issuelist[$starred_issue->getID()] = array('url' => TBGContext::getRouting()->generate('viewissue', array('project_key' => $this->selected_project->getKey(), 'issue_no' => $starred_issue->getFormattedIssueNo())), 'title' => $starred_issue->getFormattedTitle(true, true)); } } } if (array_key_exists('viewissue_list', $_SESSION) && is_array($_SESSION['viewissue_list'])) { foreach ($_SESSION['viewissue_list'] as $k => $i_id) { try { $an_issue = new TBGIssue($i_id); array_unshift($issuelist, array('url' => TBGContext::getRouting()->generate('viewissue', array('project_key' => $an_issue->getProject()->getKey(), 'issue_no' => $an_issue->getFormattedIssueNo())), 'title' => $an_issue->getFormattedTitle(true, true))); } catch (Exception $e) { unset($_SESSION['viewissue_list'][$k]); } } } if (count($issuelist) == 1) { $issuelist = null; } $this->issuelist = $issuelist; $this->issue = $issue; $event = TBGEvent::createNew('core', 'viewissue', $issue)->trigger(); $this->listenViewIssuePostError($event); }