diff --git a/CHANGELOG.md b/CHANGELOG.md index 960af12..6560193 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ Todas as mudanças relevantes deste plugin são documentadas aqui. O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/1.0.0/) e o versionamento segue [SemVer](https://semver.org/lang/pt-BR/). +## [1.5.3] + +### Corrigido +- **Vínculo gravava o ID errado do projeto Redmine**: o `merge` do Twig renumera + chaves inteiras, então o dropdown gravava o índice (posição) em vez do ID real + do projeto. As opções passam a ser montadas no PHP (preservam os IDs). + +### Adicionado +- Botão **Desvincular** na aba Redmine (projeto e entidade) — antes só dava pra vincular. +- A visão "vinculado" mostra o **nome** do projeto Redmine (não só o ID). +- Dropdown de projetos lista **apenas projetos ativos** (evita vincular projeto fechado/arquivado). + ## [1.5.2] ### Corrigido diff --git a/front/entity.form.php b/front/entity.form.php index a656cd9..6c5506f 100644 --- a/front/entity.form.php +++ b/front/entity.form.php @@ -35,6 +35,10 @@ if (isset($_POST["action"])) { ]); Session::addMessageAfterRedirect(__('Categoria padrão salva com sucesso!', 'redmine')); } + elseif ($action === 'unlink') { + $DB->delete('glpi_plugin_redmine_entities', ['entities_id' => $entities_id]); + Session::addMessageAfterRedirect(__('Entidade desvinculada do Redmine.', 'redmine')); + } elseif ($action === 'process') { $redmine_project_id = $_POST['redmine_project_id'] ?? ''; diff --git a/front/project.form.php b/front/project.form.php index faaae51..1dab564 100644 --- a/front/project.form.php +++ b/front/project.form.php @@ -39,6 +39,11 @@ if (isset($_POST["action"])) { ]); Session::addMessageAfterRedirect("Categoria padrão salva com sucesso!"); } + elseif ($action === 'unlink') { + global $DB; + $DB->delete('glpi_plugin_redmine_projects', ['projects_id' => $projects_id]); + Session::addMessageAfterRedirect("Projeto desvinculado do Redmine."); + } elseif ($action === 'process') { $redmine_project_id = $_POST['redmine_project_id'] ?? ''; diff --git a/inc/entity.class.php b/inc/entity.class.php index 2889b4d..d167c20 100644 --- a/inc/entity.class.php +++ b/inc/entity.class.php @@ -34,6 +34,9 @@ class PluginRedmineEntity extends CommonDBTM { $entities_id = $entity->getID(); $form_url = $CFG_GLPI['root_doc'] . '/plugins/redmine/front/entity.form.php'; + // {id: name} montado no PHP (Twig merge corrompe chaves inteiras). + $project_options = PluginRedmineProject::getRedmineProjectOptions(); + // Already linked? $iterator = $DB->request([ 'FROM' => 'glpi_plugin_redmine_entities', @@ -52,47 +55,30 @@ class PluginRedmineEntity extends CommonDBTM { } \Glpi\Application\View\TemplateRenderer::getInstance()->display('@redmine/project_linked.html.twig', [ - 'action_url' => $form_url, - 'owner_field' => 'entities_id', - 'owner_id' => $entities_id, - 'redmine_project_id' => $redmineProjectId, - 'cat_options' => $cat_options, - 'default_category_id' => (int) ($link['default_category_id'] ?? 0), - 'csrf_token_value' => Session::getNewCSRFToken(), + 'action_url' => $form_url, + 'owner_field' => 'entities_id', + 'owner_id' => $entities_id, + 'redmine_project_id' => $redmineProjectId, + 'redmine_project_name' => $project_options[$redmineProjectId] ?? '', + 'cat_options' => $cat_options, + 'default_category_id' => (int) ($link['default_category_id'] ?? 0), + 'csrf_token_value' => Session::getNewCSRFToken(), ]); return; } // Not linked: show the link/create form (reuses the project tab template). - $config_it = $DB->request(['FROM' => 'glpi_plugin_redmine_configs', 'WHERE' => ['id' => 1]]); - $config = count($config_it) > 0 ? $config_it->current() : []; - $metadata = !empty($config['metadata_cache']) ? json_decode($config['metadata_cache'], true) : []; - $redmine_projects = $metadata['projects'] ?? []; - - $modules = [ - 'issue_tracking' => 'Gerenciamento de Tarefas', - 'time_tracking' => 'Gerenciamento de tempo', - 'news' => 'Notícias', - 'documents' => 'Documentos', - 'files' => 'Arquivos', - 'wiki' => 'Wiki', - 'repository' => 'Repositório', - 'boards' => 'Fóruns', - 'calendar' => 'Calendário', - 'gantt' => 'Gantt' - ]; - $description = \Glpi\RichText\RichText::getTextFromHtml($entity->fields['comment'] ?? '', false, true, true); \Glpi\Application\View\TemplateRenderer::getInstance()->display('@redmine/project_form.html.twig', [ 'action_url' => $form_url, 'owner_field' => 'entities_id', 'owner_id' => $entities_id, - 'redmine_projects' => $redmine_projects, + 'project_options' => PluginRedmineProject::getRedmineProjectOptions(true), 'project_name' => $entity->fields['name'], 'project_description' => $description, 'project_identifier' => 'glpi-ent-' . $entities_id, - 'modules' => $modules, + 'modules' => PluginRedmineProject::getModules(), 'csrf_token_value' => Session::getNewCSRFToken() ]); } diff --git a/inc/project.class.php b/inc/project.class.php index d52b3f2..2f7390e 100644 --- a/inc/project.class.php +++ b/inc/project.class.php @@ -32,8 +32,13 @@ class PluginRedmineProject extends CommonDBTM { global $DB, $CFG_GLPI; $projects_id = $project->getID(); + $form_url = $CFG_GLPI['root_doc'] . '/plugins/redmine/front/project.form.php'; - // Check if already linked + // Build {id: name} of Redmine projects from cache IN PHP (Twig's merge + // renumbers integer keys, which corrupts the option values). + $project_options = self::getRedmineProjectOptions(); + + // Already linked? $iterator = $DB->request([ 'FROM' => 'glpi_plugin_redmine_projects', 'WHERE' => ['projects_id' => $projects_id] @@ -43,7 +48,6 @@ class PluginRedmineProject extends CommonDBTM { $link = $iterator->current(); $redmineProjectId = (int) $link['redmine_project_id']; - // Issue categories are per Redmine project. $cat_options = []; foreach (PluginRedmineRedmineapi::getProjectCategories($redmineProjectId) as $c) { if (isset($c['id'], $c['name'])) { @@ -52,55 +56,73 @@ class PluginRedmineProject extends CommonDBTM { } \Glpi\Application\View\TemplateRenderer::getInstance()->display('@redmine/project_linked.html.twig', [ - 'action_url' => $CFG_GLPI['root_doc'] . '/plugins/redmine/front/project.form.php', - 'owner_field' => 'projects_id', - 'owner_id' => $projects_id, - 'redmine_project_id' => $redmineProjectId, - 'cat_options' => $cat_options, - 'default_category_id' => (int) ($link['default_category_id'] ?? 0), - 'csrf_token_value' => Session::getNewCSRFToken(), + 'action_url' => $form_url, + 'owner_field' => 'projects_id', + 'owner_id' => $projects_id, + 'redmine_project_id' => $redmineProjectId, + 'redmine_project_name' => $project_options[$redmineProjectId] ?? '', + 'cat_options' => $cat_options, + 'default_category_id' => (int) ($link['default_category_id'] ?? 0), + 'csrf_token_value' => Session::getNewCSRFToken(), ]); return; } - // Fetch Redmine Metadata Cache - $config_it = $DB->request(['FROM' => 'glpi_plugin_redmine_configs', 'WHERE' => ['id' => 1]]); - $config = count($config_it) > 0 ? $config_it->current() : []; - $metadata = []; - if (!empty($config['metadata_cache'])) { - $metadata = json_decode($config['metadata_cache'], true); - } - - $redmine_projects = $metadata['projects'] ?? []; - - $modules = [ - 'issue_tracking' => 'Gerenciamento de Tarefas', - 'time_tracking' => 'Gerenciamento de tempo', - 'news' => 'Notícias', - 'documents' => 'Documentos', - 'files' => 'Arquivos', - 'wiki' => 'Wiki', - 'repository' => 'Repositório', - 'boards' => 'Fóruns', - 'calendar' => 'Calendário', - 'gantt' => 'Gantt' - ]; - - // Strip HTML from description $description = \Glpi\RichText\RichText::getTextFromHtml($project->fields['content'], false, true, true); - global $DB, $CFG_GLPI; - \Glpi\Application\View\TemplateRenderer::getInstance()->display('@redmine/project_form.html.twig', [ - 'action_url' => $CFG_GLPI['root_doc'] . '/plugins/redmine/front/project.form.php', - 'owner_field' => 'projects_id', - 'owner_id' => $projects_id, - 'redmine_projects' => $redmine_projects, - 'project_name' => $project->fields['name'], + 'action_url' => $form_url, + 'owner_field' => 'projects_id', + 'owner_id' => $projects_id, + 'project_options' => self::getRedmineProjectOptions(true), + 'project_name' => $project->fields['name'], 'project_description' => $description, - 'project_identifier' => 'glpi-proj-' . $projects_id, - 'modules' => $modules, - 'csrf_token_value' => Session::getNewCSRFToken() + 'project_identifier' => 'glpi-proj-' . $projects_id, + 'modules' => self::getModules(), + 'csrf_token_value' => Session::getNewCSRFToken() ]); } + + /** + * {id: name} map of the cached Redmine projects (PHP preserves integer keys). + * @return array + */ + static function getRedmineProjectOptions($activeOnly = false) { + global $DB; + $it = $DB->request(['FROM' => 'glpi_plugin_redmine_configs', 'WHERE' => ['id' => 1]]); + $config = count($it) > 0 ? $it->current() : []; + $metadata = !empty($config['metadata_cache']) ? json_decode($config['metadata_cache'], true) : []; + $opts = []; + foreach (($metadata['projects'] ?? []) as $rp) { + if (!isset($rp['id'], $rp['name'])) { + continue; + } + // status: 1=ativo, 5=fechado, 9=arquivado. Issues só podem ser + // criadas em projetos ativos, então o dropdown só mostra esses. + if ($activeOnly && isset($rp['status']) && (int) $rp['status'] !== 1) { + continue; + } + $opts[$rp['id']] = $rp['name']; + } + return $opts; + } + + /** + * Redmine project modules offered on creation. + * @return array + */ + static function getModules() { + return [ + 'issue_tracking' => 'Gerenciamento de Tarefas', + 'time_tracking' => 'Gerenciamento de tempo', + 'news' => 'Notícias', + 'documents' => 'Documentos', + 'files' => 'Arquivos', + 'wiki' => 'Wiki', + 'repository' => 'Repositório', + 'boards' => 'Fóruns', + 'calendar' => 'Calendário', + 'gantt' => 'Gantt' + ]; + } } diff --git a/setup.php b/setup.php index 4a6ae17..f2e775f 100644 --- a/setup.php +++ b/setup.php @@ -1,6 +1,6 @@ - {# Build a {id: name} options map from the cached Redmine projects #} - {% set rp_options = {} %} - {% for rp in redmine_projects %} - {% set rp_options = rp_options|merge({(rp.id): rp.name}) %} - {% endfor %} + {# project_options ({id: name}) é montado no PHP — não usar merge no Twig, + que renumera chaves inteiras e corrompe os valores das opções. #}
@@ -25,7 +22,7 @@
- {{ fields.dropdownArrayField('redmine_project_id', '', rp_options, __('Vincular a Projeto Existente', 'redmine'), { + {{ fields.dropdownArrayField('redmine_project_id', '', project_options, __('Vincular a Projeto Existente', 'redmine'), { 'display_emptychoice': true, 'full_width': true }) }} @@ -51,7 +48,7 @@ 'helper': __('Projetos públicos são visíveis para todos.', 'redmine') }) }} - {{ fields.dropdownArrayField('parent_id', '', rp_options, __('Subprojeto de', 'redmine'), { + {{ fields.dropdownArrayField('parent_id', '', project_options, __('Subprojeto de', 'redmine'), { 'display_emptychoice': true, 'full_width': true }) }} diff --git a/templates/project_linked.html.twig b/templates/project_linked.html.twig index 197d6d2..69de274 100644 --- a/templates/project_linked.html.twig +++ b/templates/project_linked.html.twig @@ -1,8 +1,20 @@ {% import 'components/form/fields_macros.html.twig' as fields %} -
- - {{ __('Projeto vinculado à issue Redmine de ID', 'redmine') }} #{{ redmine_project_id }} +
+ + + {{ __('Vinculado ao projeto Redmine', 'redmine') }} + {{ redmine_project_name|default('') }} (#{{ redmine_project_id }}) + +
+ + + + +