Redmine/inc/config.class.php
Gemini 80d1576a80 Redmine Integration 1.3.0
Plugin de integração GLPI <-> Redmine:
- Config nativa (Twig) com API key cifrada (GLPIKey)
- Vínculo projeto GLPI <-> projeto Redmine (aba do projeto) + categoria por vínculo
- 1 issue Redmine por chamado; defaults (tracker/priority/activity) e mapa de status
- Tempo das TicketTasks -> time entries (gate em "Feito"), idempotente
- Autores via impersonation (X-Redmine-Switch-User) + auto-membership por role
- Mapeamento de usuários GLPI->Redmine (auto-match por e-mail + overrides)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 18:32:19 -03:00

206 lines
7.5 KiB
PHP

<?php
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
class PluginRedmineConfig extends CommonDBTM {
/**
* Define the tab name
* @param int $nb
* @return string
*/
static function getTypeName($nb = 0) {
return 'Redmine Integration';
}
/**
* Encrypt the API key before persisting it.
* An empty key on update means "keep the current one".
* @param array $input
* @return array
*/
function prepareInputForUpdate($input) {
if (array_key_exists('api_key', $input)) {
if ($input['api_key'] === '' || $input['api_key'] === null) {
unset($input['api_key']); // keep the stored key
} else {
$input['api_key'] = (new \GLPIKey())->encrypt((string) $input['api_key']);
}
}
// The status map is posted as status_map[<glpi_status>] = <redmine_status_id>.
if (isset($input['status_map']) && is_array($input['status_map'])) {
$input['status_map'] = json_encode($input['status_map']);
}
// Default watchers come from a multiselect (array of Redmine user ids).
if (isset($input['default_watcher_ids'])) {
$ids = is_array($input['default_watcher_ids']) ? $input['default_watcher_ids'] : [];
$input['default_watcher_ids'] = json_encode(array_values(array_filter(array_map('intval', $ids))));
}
return $input;
}
/**
* Return the (single) config row.
* @return array
*/
public static function getConfigRow() {
global $DB;
$it = $DB->request(['FROM' => 'glpi_plugin_redmine_configs', 'WHERE' => ['id' => 1]]);
return count($it) > 0 ? $it->current() : [];
}
/**
* Resolve the Redmine user for a GLPI user.
* Priority: manual override (usermap) -> e-mail auto-match against the cache.
* @param int $glpi_users_id
* @return array|null Redmine user cache row [redmine_id, login, mail, name] or null
*/
public static function resolveRedmineUser($glpi_users_id) {
global $DB;
$glpi_users_id = (int) $glpi_users_id;
if ($glpi_users_id <= 0) {
return null;
}
// 1) Manual override
$ov = $DB->request([
'FROM' => 'glpi_plugin_redmine_usermap',
'WHERE' => ['users_id' => $glpi_users_id]
]);
if (count($ov) > 0) {
$rid = (int) $ov->current()['redmine_user_id'];
$ru = $DB->request(['FROM' => 'glpi_plugin_redmine_users', 'WHERE' => ['redmine_id' => $rid]]);
if (count($ru) > 0) {
return $ru->current();
}
}
// 2) E-mail auto-match
$email = \UserEmail::getDefaultForUser($glpi_users_id);
if (!empty($email)) {
$ru = $DB->request(['FROM' => 'glpi_plugin_redmine_users', 'WHERE' => ['mail' => $email]]);
if (count($ru) > 0) {
return $ru->current();
}
}
return null;
}
/**
* @return int default Redmine role id used for auto-membership
*/
public static function getDefaultRoleId() {
return (int) (self::getConfigRow()['default_role_id'] ?? 0);
}
/**
* @return int[] configured default watcher Redmine user ids
*/
public static function getDefaultWatcherIds() {
$raw = self::getConfigRow()['default_watcher_ids'] ?? '';
if (empty($raw)) {
return [];
}
$ids = json_decode($raw, true);
return is_array($ids) ? array_values(array_map('intval', $ids)) : [];
}
/**
* Map a GLPI ticket status to the configured Redmine status id.
* @param int $glpi_status
* @return int|null
*/
public static function mapTicketStatus($glpi_status) {
$row = self::getConfigRow();
if (empty($row['status_map'])) {
return null;
}
$map = json_decode($row['status_map'], true);
if (!is_array($map) || empty($map[$glpi_status])) {
return null;
}
return (int) $map[$glpi_status];
}
/**
* Build a {id: name} options map from a Redmine metadata list.
* @param array $list
* @return array
*/
private static function toOptions($list) {
$opt = [];
foreach (($list ?? []) as $el) {
if (isset($el['id'], $el['name'])) {
$opt[$el['id']] = $el['name'];
}
}
return $opt;
}
/**
* Show config form
* @param int $ID
* @param array $options
* @return boolean
*/
function showForm($ID, array $options = []) {
global $DB;
// Fetch config
$config = [];
$iterator = $DB->request([
'FROM' => 'glpi_plugin_redmine_configs',
'WHERE' => ['id' => 1]
]);
if (count($iterator) > 0) {
$config = $iterator->current();
}
$metadata = !empty($config['metadata_cache']) ? json_decode($config['metadata_cache'], true) : [];
$status_map = !empty($config['status_map']) ? json_decode($config['status_map'], true) : [];
$watcher_ids = self::getDefaultWatcherIds();
// Redmine users (from cache) -> "Nome (login)" options
$redmine_user_options = [];
foreach ($DB->request(['FROM' => 'glpi_plugin_redmine_users', 'ORDER' => 'name']) as $u) {
$redmine_user_options[$u['redmine_id']] = trim($u['name']) . ' (' . $u['login'] . ')';
}
// Existing manual overrides (GLPI user -> Redmine user) with display names
$usermap = [];
foreach ($DB->request(['FROM' => 'glpi_plugin_redmine_usermap']) as $row) {
$usermap[] = [
'id' => $row['id'],
'users_id' => $row['users_id'],
'glpi_name' => getUserName($row['users_id']),
'redmine_name' => $redmine_user_options[$row['redmine_user_id']] ?? ('#' . $row['redmine_user_id']),
];
}
\Glpi\Application\View\TemplateRenderer::getInstance()->display('@redmine/config_form.html.twig', [
'action_url' => Toolbox::getItemTypeFormURL(__CLASS__),
'redmine_url' => $config['redmine_url'] ?? '',
'has_key' => !empty($config['api_key']),
'has_metadata' => !empty($metadata['trackers']) || !empty($metadata['statuses']),
'tracker_options' => self::toOptions($metadata['trackers'] ?? []),
'priority_options' => self::toOptions($metadata['priorities'] ?? []),
'activity_options' => self::toOptions($metadata['activities'] ?? []),
'status_options' => self::toOptions($metadata['statuses'] ?? []),
'role_options' => self::toOptions($metadata['roles'] ?? []),
'redmine_user_options' => $redmine_user_options,
'default_tracker_id' => (int) ($config['default_tracker_id'] ?? 0),
'default_priority_id' => (int) ($config['default_priority_id'] ?? 0),
'default_activity_id' => (int) ($config['default_activity_id'] ?? 0),
'default_role_id' => (int) ($config['default_role_id'] ?? 0),
'default_watcher_ids' => $watcher_ids,
'status_map' => is_array($status_map) ? $status_map : [],
'glpi_ticket_statuses' => \Ticket::getAllStatusArray(),
'usermap' => $usermap,
]);
return true;
}
}