getDescription()); $config = $services->get('config'); $logger = $services->get('logger'); foreach ($callouts as $callout) { if (!array_key_exists('issues', $callout) || !array_key_exists('commands', $callout)) { continue; } foreach ($callout['issues'] as $issue) { $commentCommand = self::getCommand('comment', $callout['commands']); $timeCommand = self::getCommand('time', $callout['commands']); $transitionCommands = self::getTransitionCommands($callout['commands']); if ($commentCommand && array_key_exists('args', $commentCommand)) { $logger->info('JiraSmartCommits: building comment'); if (ConfigManager::getValue($config, 'jirasmartcommits.cite_submitter_username', true)) { $prefix = "[~" . $item->getUser() . "] says in c"; } else { $prefix = "C"; } if (ConfigManager::getValue($config, 'jirasmartcommits.link_changelist_comment_reference', true)) { $qualifiedUrl = $services->get('ViewHelperManager')->get('qualifiedUrl'); $changelist = "[" . $item->getId() . "|" . $qualifiedUrl('change', array('change' => $item->getId())) . "]"; } else { $changelist = $item->getId(); } $commentStr = "${prefix}hangelist $changelist: ${commentCommand['args']}"; $commentObj = array( 'body' => $commentStr, ); } if ($timeCommand && array_key_exists('args', $timeCommand)) { $logger->info('JiraSmartCommits: building time tracking'); $msg = array( 'timeSpent' => $timeCommand['args'], ); if (isset($commentStr)) { $logger->info('JiraSmartCommits: and bundling comment'); $msg['comment'] = $commentStr; $commented = true; $timeCommented = true; } if (self::doRequest( 'post', "issue/$issue/worklog", $msg, $services) === false && isset($timeCommented)) { // if time-tracking fails but we wanted a comment, try to still get the comment posted unset($commented); } unset($timeCommented); } if (count($transitionCommands) > 0) { $logger->info('JiraSmartCommits: asked for transition(s)'); $availableTransitions = self::doRequest( 'get', "issue/$issue/transitions", null, $services); $transitionId = null; if ($availableTransitions && is_array($availableTransitions) && array_key_exists('transitions', $availableTransitions) && is_array($availableTransitions['transitions'])) { foreach ($availableTransitions['transitions'] as $availableTransition) { if (is_array($availableTransition) && array_key_exists('name', $availableTransition)) { $availableTransitionName = str_replace(' ', '-', strtolower($availableTransition['name'])); $logger->debug("JiraSmartCommits: available transition: $availableTransitionName"); foreach ($transitionCommands as $transitionCommand) { $logger->debug("JiraSmartCommits: comparing to ${transitionCommand['command']}"); if (strpos($availableTransitionName, $transitionCommand['command']) === 0) { $transitionId = $availableTransition['id']; break; } } if ($transitionId) { break; } } } } if ($transitionId) { $logger->info("JiraSmartCommits: found desired transition id $transitionId"); $msg = array( 'transition' => array( 'id' => $transitionId, ), ); if (isset($commentObj) && !isset($commented)) { $logger->info('JiraSmartCommits: bundling comment'); $msg['update'] = array( 'comment' => array( array( 'add' => $commentObj, ), ), ); $commented = true; $transitionCommented = true; } if (self::doRequest( 'post', "issue/$issue/transitions", $msg, $services) === false && isset($transitionCommented)) { // if resolving fails but we wanted a comment, try to still get the comment posted unset($commented); } } else { $logger->notice('JiraSmartCommits: unable to locate a valid transition id. ' . print_r($transitionCommands, true) . print_r($availableTransitions, true)); } unset($transitionCommented); } if (isset($commentObj) && !isset($commented)) { $logger->info('JiraSmartCommits: comment wanted, not already handled. handling.'); self::doRequest( 'post', "issue/$issue/comment", $commentObj, $services); } unset($commented, $commentObj, $commentStr); } } } private static function getCommand($key, $commandArray) { foreach ($commandArray as $command) { if (is_array($command) && array_key_exists('command', $command) && $command['command'] == $key) { return $command; } } return null; } private static function getTransitionCommands($commandArray) { $ret = array(); foreach ($commandArray as $command) { if (is_array($command) && array_key_exists('command', $command) && $command['command'] != 'comment' && $command['command'] != 'time') { array_push($ret, $command); } } return $ret; } public static function getJiraCallouts($value) { $projects = array_map('preg_quote', self::getProjects()); $callouts = array(); foreach (explode("\n", $value) as $line) { $mode = 0; foreach (preg_split('/(\s+)/', $line) as $word) { $issues = self::getJiraIssue($word, $projects); $command = self::getSmartCommitCommand($word); if (!isset($last)) { $last = array(); } if (count($callouts) > 0) { $last = &$callouts[count($callouts)-1]; } if ($command && ($mode === 1 || $mode === 2)) { if (array_key_exists('commands', $last)) { $last['commands'][] = array('command' => $command); } else { $last['commands'] = array(array('command' => $command)); } $mode = 2; } elseif ($issues) { if ($mode === 0 || !array_key_exists('issues', $last) || array_key_exists('commands', $last)) { $callouts[] = array('issues' => $issues); } else { $last['issues'] = array_merge($last['issues'], $issues); } $mode = 1; } elseif ($mode === 2) { $lastCommand = &$last['commands'][count($last['commands'])-1]; if (array_key_exists('args', $lastCommand)) { $lastCommand['args'] .= ' '; } else { $lastCommand['args'] = ''; } $lastCommand['args'] .= $word; } } } return $callouts; } public static function getJiraIssue($value, $projects) { if (preg_match_all("/((?:" . implode('|', $projects) . ")-[0-9]+)/", $value, $match)) { return $match[1]; } return null; } public static function getSmartCommitCommand($value) { if (strpos($value, '#') === 0 && strlen($value) > 1) { return strtolower(substr($value, 1)); } return null; } public static function doRequest($method, $resource, $data, ServiceLocator $services) { // we commonly do a number of requests and don't want one failure to bork them all, // if anything goes wrong just log it try { // setup the client and request details $config = $services->get('config'); $url = ConfigManager::getValue($config, 'jirasmartcommits.host') . '/rest/api/latest/' . $resource; $client = new HttpClient; $client->setUri($url) ->setHeaders(array('Content-Type' => 'application/json')) ->setMethod($method); // set the http client options; including any special overrides for our host $options = $services->get('config') + array('http_client_options' => array()); $options = (array) $options['http_client_options']; if (isset($options['hosts'][$client->getUri()->getHost()])) { $options = (array) $options['hosts'][$client->getUri()->getHost()] + $options; } unset($options['hosts']); $client->setOptions($options); if ($method == 'post') { $client->setRawBody(Json::encode($data)); } else { $client->setParameterGet((array) $data); } if (ConfigManager::getValue($config, 'jirasmartcommits.user')) { $client->setAuth(ConfigManager::getValue($config, 'jirasmartcommits.user'), ConfigManager::getValue($config, 'jirasmartcommits.password')); } // attempt the request and log any errors $services->get('logger')->info('JiraSmartCommits making ' . $method . ' request to resource: ' . $url, (array) $data); $response = $client->dispatch($client->getRequest()); if (!$response->isSuccess()) { $services->get('logger')->err( 'JiraSmartCommits failed to ' . $method . ' resource: ' . $url . ' (' . $response->getStatusCode() . " - " . $response->getReasonPhrase() . ').', array( 'request' => $client->getLastRawRequest(), 'response' => $client->getLastRawResponse() ) ); return false; } // looks like it worked, return the result return json_decode($response->getBody(), true); } catch (\Exception $e) { // the Jira module is doing this, but it seems to be generating its own exceptions for me. // fortunately the error is already logged from above, so we're good. //$services->get('logger')->err($e); } return false; } public static function getProjects() { $file = DATA_PATH . '/cache/jirasmartcommits/projects'; if (!file_exists($file)) { return array(); } return (array) json_decode(file_get_contents($file), true); } public static function getCacheDir() { $dir = DATA_PATH . '/cache/jirasmartcommits'; if (!is_dir($dir)) { @mkdir($dir, 0700, true); } if (!is_writable($dir)) { @chmod($dir, 0700); } if (!is_dir($dir) || !is_writable($dir)) { throw new \RuntimeException( "Cannot write to cache directory ('" . $dir . "'). Check permissions." ); } return $dir; } public function getConfig() { return include __DIR__ . '/config/module.config.php'; } }