encrypt((string) $input['api_key']); } } // The status map is posted as status_map[] = . 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 (cache local) $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(); } // 3) Self-healing: cache pode estar vazio/desatualizado — busca ao // vivo no Redmine e grava no cache para as próximas resoluções. $live = PluginRedmineRedmineapi::findUserByEmail($email); if ($live && !empty($live['login'])) { $row = [ 'redmine_id' => (int) $live['id'], 'login' => $live['login'], 'mail' => $live['mail'] ?? $email, 'name' => trim(($live['firstname'] ?? '') . ' ' . ($live['lastname'] ?? '')), ]; $DB->delete('glpi_plugin_redmine_users', ['redmine_id' => $row['redmine_id']]); $DB->insert('glpi_plugin_redmine_users', $row); Toolbox::logInFile('redmine', "Cache de usuários atualizado ao vivo para '{$email}' -> {$row['login']}.\n"); return $row; } } 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__), 'csrf_token_value' => Session::getNewCSRFToken(), '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, 'user_cache_count' => count($redmine_user_options), ]); return true; } }