Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php if (!defined('ROOTPATH')) exit('No direct script access allowed'); ?>
- <?php
- #Include the FirePHP class
- require_once('FirePHPCore/FirePHP.class.php');
- #Start buffering the output. Not required if output_buffering is set on in php.ini file
- ob_start();
- /**
- * Redmine Defect Plugin for TestRail
- *
- * Copyright Gurock Software GmbH. All rights reserved.
- *
- * This is the TestRail defect plugin for Redmine. Please see
- * http://docs.gurock.com/testrail-integration/defects-plugins for
- * more information about TestRail's defect plugins.
- *
- * http://www.gurock.com/testrail/
- */
- class Redmine_custom_defect_plugin extends Defect_plugin
- {
- private $_api;
- private $_address;
- private $_user;
- private $_password;
- private $_apikey;
- private $_is_legacy = false;
- private $_trackers;
- private $_categories;
- private static $_meta = array(
- 'author' => 'Gurock Software',
- 'version' => '1.0',
- 'description' => 'Redmine defect plugin for TestRail',
- 'can_push' => true,
- 'can_lookup' => true,
- 'default_config' =>
- '; Please configure your Redmine connection below
- ; For Redmine versions older than 1.3, you need to
- ; activate the legacy mode of this plugin. Please
- ; contact the Gurock Software support in case you
- ; have any questions or refer to the documentation:
- ; http://on.gurock.com/redmine35
- [connection]
- address=http://10.3.1.152:81/redmine/
- apikey=%redmine_apikey%'
- );
- public function get_meta()
- {
- return self::$_meta;
- }
- // *********************************************************
- // CONFIGURATION
- // *********************************************************
- public function validate_config($config)
- {
- $ini = ini::parse($config);
- if (!isset($ini['connection']))
- {
- throw new ValidationException('Missing [connection] group');
- }
- //$keys = array('address', 'user', 'password');
- $keys = array('address','apikey');
- // Check required values for existance
- foreach ($keys as $key)
- {
- if (!isset($ini['connection'][$key]) ||
- !$ini['connection'][$key])
- {
- throw new ValidationException(
- "Missing configuration for key '$key'"
- );
- }
- }
- $address = $ini['connection']['address'];
- // Check whether the address is a valid url (syntax only)
- if (!check::url($address))
- {
- throw new ValidationException('Address is not a valid url');
- }
- if (isset($ini['connection']['mode']))
- {
- // Mode must be set to 'legacy' when available.
- if ($ini['connection']['mode'] != 'legacy')
- {
- throw new ValidationException(
- 'Mode given but not set to "legacy"'
- );
- }
- if (!isset($ini['trackers']))
- {
- throw new ValidationException(
- 'Using legacy mode but [trackers] is missing'
- );
- }
- if (!isset($ini['categories']))
- {
- throw new ValidationException(
- 'Using legacy mode but [categories] is missing'
- );
- }
- }
- }
- public function configure($config)
- {
- $ini = ini::parse($config);
- $this->_address = str::slash($ini['connection']['address']);
- //$this->_user = $ini['connection']['user'];
- //$this->_password = $ini['connection']['password'];
- $this->_apikey = $ini['connection']['apikey'];
- if (isset($ini['connection']['mode']))
- {
- $this->_is_legacy = true;
- $this->_trackers = $ini['trackers'];
- $this->_categories = $this->_parse_categories(
- $ini['categories']);
- }
- }
- private function _parse_categories($ini)
- {
- $categories = array();
- // Uses the given ini section with keys 'project_id.item_id'
- // to create a category key => value mapping for the given
- // projects.
- foreach ($ini as $key => $value)
- {
- if (preg_match('/^([^\.]+)\.([^\.]+)$/', $key, $matches))
- {
- $project_id = (int) $matches[1];
- $item_id = (int) $matches[2];
- $categories[$project_id][$item_id] = $value;
- }
- }
- return $categories;
- }
- // *********************************************************
- // API / CONNECTION
- // *********************************************************
- private function _get_api()
- {
- if ($this->_api)
- {
- return $this->_api;
- }
- $this->_api = new Redmine_api(
- $this->_address, $this->_apikey);
- //$this->_user,
- //$this->_password);
- return $this->_api;
- }
- // *********************************************************
- // PUSH
- // *********************************************************
- public function prepare_push($context)
- {
- // Return a form with the following fields/properties
- return array(
- 'fields' => array(
- 'subject' => array(
- 'type' => 'string',
- 'label' => 'Subject',
- 'required' => true,
- 'size' => 'full'
- ),
- 'tracker' => array(
- 'type' => 'dropdown',
- 'label' => 'Tracker',
- 'required' => true,
- 'remember' => true,
- 'size' => 'compact'
- ),
- 'project' => array(
- 'type' => 'dropdown',
- 'label' => 'Project',
- 'required' => true,
- 'remember' => true,
- 'cascading' => true,
- 'size' => 'compact'
- ),
- 'category' => array(
- 'type' => 'dropdown',
- 'label' => 'Category',
- 'remember' => true,
- 'depends_on' => 'project',
- 'size' => 'compact'
- ),
- 'os' => array(
- 'type' => 'dropdown',
- 'label' => 'OS',
- 'remember' => true,
- 'size' => 'compact'
- ),
- 'ostype' => array(
- 'type' => 'dropdown',
- 'label' => 'OS típusa',
- 'remember' => true,
- 'size' => 'compact'
- ),
- 'reproducibility' => array(
- 'type' => 'dropdown',
- 'label' => 'Reprodukálhatóság',
- 'remember' => true,
- 'size' => 'compact'
- ),
- 'severity' => array(
- 'type' => 'dropdown',
- 'label' => 'Súlyosság',
- 'remember' => true,
- 'size' => 'compact'
- ),
- 'stepstoreproduce' => array(
- 'type' => 'text',
- 'label' => 'Hibához szükséges lépések',
- 'rows' => 10
- ),
- 'description' => array(
- 'type' => 'text',
- 'label' => 'Description',
- 'rows' => 10
- )
- )
- );
- }
- private function _get_subject_default($context)
- {
- $test = current($context['tests']);
- $subject = 'Failed test: ' . $test->case->title;
- if ($context['test_count'] > 1)
- {
- $subject .= ' (+others)';
- }
- return $subject;
- }
- private function _get_description_default($context)
- {
- return $context['test_change']->description;
- }
- private function _to_id_name_lookup($items)
- {
- $result = array();
- foreach ($items as $item)
- {
- $result[$item->id] = $item->name;
- }
- return $result;
- }
- //Get all possible value for the custom field
- private function _get_possible_values($items, $name)
- {
- #get a firePHP variable reference
- $firephp = FirePHP::getInstance(true);
- $firephp->log($items, '_get_possible_values for '.$name.': items');
- $result = array();
- foreach ($items as $item)
- {
- if($item->name == $name)
- {
- //$i = 0;
- foreach($item->possible_values as $value)
- {
- $result[$value->value] = $value->value;
- //$i++;
- }
- }
- }
- $firephp->log($result, '_get_possible_values for '.$name.': result');
- return $result;
- }
- private function _get_trackers($api)
- {
- // In legacy mode for Redmine versions older than 1.3, we use
- // the user-configured values for the trackers. Otherwise,
- // we can just use the API.
- if ($this->_is_legacy)
- {
- if (is_array($this->_trackers))
- {
- return $this->_trackers;
- }
- else
- {
- return null;
- }
- }
- else
- {
- return $this->_to_id_name_lookup(
- $api->get_trackers()
- );
- }
- }
- private function _get_categories($api, $project_id)
- {
- // In legacy mode for Redmine versions older than 1.3, we use
- // the user-configured values for the categories. Otherwise,
- // we can just use the API.
- if ($this->_is_legacy)
- {
- $categories = arr::get($this->_categories, $project_id);
- if (!is_array($categories))
- {
- return null;
- }
- return $categories;
- }
- else
- {
- return $this->_to_id_name_lookup(
- $api->get_categories($project_id)
- );
- }
- }
- #GET custom field possible values from redmine
- private function _get_operating_systems($api)
- {
- return $this->_get_possible_values(
- $api->get_custom_fields(), 'OS'
- );
- }
- private function _get_operating_systems_types($api)
- {
- return $this->_get_possible_values(
- $api->get_custom_fields(), 'OS típusa'
- );
- }
- private function _get_reproducibility($api)
- {
- return $this->_get_possible_values(
- $api->get_custom_fields(), 'Reprodukálhatóság'
- );
- }
- private function _get_severity($api)
- {
- return $this->_get_possible_values(
- $api->get_custom_fields(), 'Súlyosság'
- );
- }
- private function _get_stepstoreproduce($api)
- {
- return $this->_get_possible_values(
- $api->get_custom_fields(), 'Hibához szükséges lépések'
- );
- }
- public function prepare_field($context, $input, $field)
- {
- $data = array();
- // Take into account the preferences of the user, but only
- // for the initial form rendering (not for dynamic loads).
- if ($context['event'] == 'prepare')
- {
- $prefs = arr::get($context, 'preferences');
- }
- else
- {
- $prefs = null;
- }
- // Process those fields that do not need a connection to the
- // Redmine installation.
- if ($field == 'subject' || $field == 'description')
- {
- switch ($field)
- {
- case 'subject':
- $data['default'] = $this->_get_subject_default(
- $context);
- break;
- case 'description':
- $data['default'] = $this->_get_description_default(
- $context);
- break;
- }
- return $data;
- }
- // And then try to connect/login (in case we haven't set up a
- // working connection previously in this request) and process
- // the remaining fields.
- $api = $this->_get_api();
- switch ($field)
- {
- case 'tracker':
- $data['default'] = arr::get($prefs, 'tracker');
- $data['options'] = $this->_get_trackers($api);
- break;
- case 'project':
- $data['default'] = arr::get($prefs, 'project');
- $data['options'] = $this->_to_id_name_lookup(
- $api->get_projects()
- );
- break;
- case 'category':
- if (isset($input['project']))
- {
- $data['default'] = arr::get($prefs, 'category');
- $data['options'] = $this->_get_categories($api,
- $input['project']);
- }
- break;
- case 'os':
- $data['default'] = arr::get($prefs, 'os');
- $data['options'] = $this->_get_operating_systems($api);
- break;
- case 'ostype':
- $data['default'] = arr::get($prefs, 'ostype');
- $data['options'] = $this->_get_operating_systems_types($api);
- break;
- case 'reproducibility':
- $data['default'] = arr::get($prefs, 'reproducibility');
- $data['options'] = $this->_get_reproducibility($api);
- break;
- case 'severity':
- $data['default'] = arr::get($prefs, 'severity');
- $data['options'] = $this->_get_severity($api);
- break;
- case 'stepstoreproduce':
- $data['default'] = arr::get($prefs, 'stepstoreproduce');
- //$data['options'] = $this->_get_stepstoreproduce($api);
- break;
- }
- return $data;
- }
- public function validate_push($context, $input)
- {
- }
- public function push($context, $input)
- {
- $api = $this->_get_api();
- #get a firePHP variable reference
- $firephp = FirePHP::getInstance(true);
- $firephp->log($context, 'push: context');
- $firephp->log($input, 'push: input');
- return $api->add_issue($input);
- }
- // *********************************************************
- // LOOKUP
- // *********************************************************
- public function lookup($defect_id)
- {
- $api = $this->_get_api();
- $issue = $api->get_issue($defect_id);
- $status_id = GI_DEFECTS_STATUS_OPEN;
- if (isset($issue->status))
- {
- $status = $issue->status->name;
- // Redmine's status API is only available in Redmine 1.3
- // or later, unfortunately, so we can only try to guess
- // by its name.
- switch (str::to_lower($status))
- {
- case 'resolved':
- $status_id = GI_DEFECTS_STATUS_RESOLVED;
- break;
- case 'closed':
- $status_id = GI_DEFECTS_STATUS_CLOSED;
- break;
- }
- }
- else
- {
- $status = null;
- }
- if (isset($issue->description) && $issue->description)
- {
- $description = str::format(
- '<div class="monospace">{0}</div>',
- nl2br(
- html::link_urls(
- h($issue->description)
- )
- )
- );
- }
- else
- {
- $description = null;
- }
- // Add some important attributes for the issue such as the
- // current status and project.
- $attributes = array();
- if (isset($issue->tracker))
- {
- $attributes['Tracker'] = h($issue->tracker->name);
- }
- if ($status)
- {
- $attributes['Status'] = h($status);
- }
- if (isset($issue->project))
- {
- // Add a link back to the project (issue list).
- $attributes['Project'] = str::format(
- '<a target="_blank" href="{0}projects/{1}">{2}</a>',
- a($this->_address),
- a($issue->project->id),
- h($issue->project->name)
- );
- }
- if (isset($issue->category))
- {
- $attributes['Category'] = h($issue->category->name);
- }
- #get a firePHP variable reference
- $firephp = FirePHP::getInstance(true);
- $firephp->log($issue, 'lookup: issue');
- //Gets all the custom fields and their content
- if (isset($issue->custom_fields))
- {
- foreach($issue->custom_fields as $field){
- $attributes[$field->name] = h($field->value);
- }
- }
- return array(
- 'id' => $defect_id,
- 'url' => str::format(
- '{0}issues/{1}',
- $this->_address,
- $defect_id
- ),
- 'title' => $issue->subject,
- 'status_id' => $status_id,
- 'status' => $status,
- 'description' => $description,
- 'attributes' => $attributes
- );
- }
- }
- /**
- * Redmine API
- *
- * Wrapper class for the Redmine API with functions for retrieving
- * projects, bugs etc. from a Redmine installation.
- */
- class Redmine_api
- {
- private $_address;
- //private $_user;
- //private $_password;
- private $_apikey;
- private $_version;
- private $_curl;
- private $_custom_fields;
- /**
- * Construct
- *
- * Initializes a new Redmine API object. Expects the web address
- * of the Redmine installation including http or https prefix.
- */
- //public function __construct($address, $user, $password)
- public function __construct($address, $apikey)
- {
- $this->_address = str::slash($address);
- //$this->_user = $user;
- //$this->_password = $password;
- $this->_apikey = $apikey;
- }
- private function _throw_error($format, $params = null)
- {
- $args = func_get_args();
- $format = array_shift($args);
- if (count($args) > 0)
- {
- $message = str::formatv($format, $args);
- }
- else
- {
- $message = $format;
- }
- throw new RedmineException($message);
- }
- private function _send_command($method, $command, $data = array())
- {
- $url = $this->_address . $command . '.json';
- if ($method == 'GET')
- {
- $url .= '?limit=100';
- }
- return $this->_send_request($method, $url, $data);
- }
- private function _send_request($method, $url, $data)
- {
- if (!$this->_curl)
- {
- // Initialize the cURL handle. We re-use this handle to
- // make use of Keep-Alive, if possible.
- $this->_curl = http::open();
- }
- $response = http::request_ex(
- $this->_curl,
- $method,
- $url,
- array(
- 'data' => $data,
- 'headers' => array(
- 'Content-Type' => 'application/json',
- 'X-Redmine-API-Key' => $this->_apikey
- )
- // 'user' => $this->_user,
- // 'password' => $this->_password,
- // 'headers' => array(
- // 'Content-Type' => 'application/json'
- // ) 'X-Redmine-API-Key' => $this->_apikey
- )
- );
- #get a firePHP variable reference
- $firephp = FirePHP::getInstance(true);
- $firephp->log($response, '_send_request: response');
- // In case debug logging is enabled, we append the data
- // we've sent and the entire request/response to the log.
- if (logger::is_on(GI_LOG_LEVEL_DEBUG))
- {
- logger::debugr('$data', $data);
- logger::debugr('$response', $response);
- }
- $obj = json::decode($response->content);
- if ($response->code != 200)
- {
- if ($response->code != 201) // Created
- {
- $this->_throw_error(
- 'Invalid HTTP code ({0}). Please check your user/' .
- 'password and that the API is enabled in Redmine.',
- $response->code
- );
- }
- }
- return $obj;
- }
- /**
- * Get Issue
- *
- * Gets an existing issue from the Redmine installation and
- * returns it. The resulting issue object has various properties
- * such as the subject, description, project etc.
- */
- public function get_issue($issue_id)
- {
- $response = $this->_send_command(
- 'GET', 'issues/' . urlencode($issue_id)
- );
- return $response->issue;
- }
- /**
- * Get Trackers
- *
- * Gets the available trackers for the Redmine installation.
- * Trackers are returned as array of objects, each with its ID
- * and name. Requires Redmine >= 1.3.
- */
- public function get_trackers()
- {
- $response = $this->_send_command('GET', 'trackers');
- return $response->trackers;
- }
- /**
- * Get Projects
- *
- * Gets the available projects for the Redmine installation.
- * Projects are returned as array of objects, each with its ID
- * and name.
- */
- public function get_projects()
- {
- $response = $this->_send_command('GET', 'projects');
- return $response->projects;
- }
- /**
- * Get Custom fields
- */
- public function get_custom_fields()
- {
- $firephp = FirePHP::getInstance(true);
- $firephp->log($_custom_fields, 'get_custom_fields: _custom_fields');
- if(!isset($_custom_fields)){
- $response = $this->_send_command('GET', 'custom_fields');
- $firephp->log($response, 'get_custom_fields: response');
- $_custom_fields = $response->custom_fields;
- }
- return $_custom_fields;
- }
- /**
- * Get Categories
- *
- * Gets the available categories for the given project ID for the
- * Redmine installation. Categories are returned as array of
- * objects, each with its ID and name. Requires Redmine >= 1.3.
- */
- public function get_categories($project_id)
- {
- $firephp = FirePHP::getInstance(true);
- $firephp->log($project_id, 'get_categories: project_id');
- $response = $this->_send_command('GET',
- "projects/$project_id/issue_categories");
- $firephp->log($response, 'get_categories: response');
- return $response->issue_categories;
- }
- /**
- * Add Issue
- *
- * Adds a new issue to the Redmine installation with the given
- * parameters (subject, project etc.) and returns its ID.
- *
- * subject: The title of the new issue
- * tracker: The ID of the tracker of the new issue (bug,
- * feature request etc.)
- * project: The ID of the project the issue should be added
- * to
- * category: The ID of the category the issue is added to
- * description: The description of the new issue
- */
- public function add_issue($options)
- {
- $issue = obj::create();
- $issue->subject = $options['subject'];
- $issue->description = $options['description'];
- $issue->tracker_id = $options['tracker'];
- $issue->project_id = $options['project'];
- $custom_fields_array = array();
- array_push($custom_fields_array, array('id' => 1 ,'value' => $options['os']));
- array_push($custom_fields_array, array('id' => 5 ,'value' => $options['ostype']));
- array_push($custom_fields_array, array('id' => 2 ,'value' => $options['reproducibility']));
- array_push($custom_fields_array, array('id' => 3 ,'value' => $options['severity']));
- array_push($custom_fields_array, array('id' => 4 ,'value' => $options['stepstoreproduce']));
- $issue->custom_fields = $custom_fields_array;
- #get a firePHP variable reference
- $firephp = FirePHP::getInstance(true);
- $firephp->log($issue, 'add_issue: issue');
- if ($options['category'])
- {
- $issue->category_id = $options['category'];
- }
- $data = json::encode(array('issue' => $issue));
- $firephp->log($data, 'add_issue: data');
- $response = $this->_send_command('POST', 'issues', $data);
- return $response->issue->id;
- $firephp->log($response, 'add_issue: response');
- }
- }
- class RedmineException extends Exception
- {
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement