fix: dropdown gravava índice (Twig merge) em vez do ID real do projeto (1.5.3)
- opções do projeto Redmine montadas no PHP (Twig merge renumera chaves inteiras) - aba mostra o NOME do projeto vinculado (não só o ID) - botão Desvincular (projeto e entidade) + dropdown só com projetos ativos Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ecf175728f
commit
6f844e7336
8 changed files with 119 additions and 81 deletions
12
CHANGELOG.md
12
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
|
||||
|
|
|
|||
|
|
@ -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'] ?? '';
|
||||
|
||||
|
|
|
|||
|
|
@ -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'] ?? '';
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
define('PLUGIN_REDMINE_VERSION', '1.5.2');
|
||||
define('PLUGIN_REDMINE_VERSION', '1.5.3');
|
||||
|
||||
/**
|
||||
* Load the Mindplace License class if the autoloader has not run yet.
|
||||
|
|
|
|||
|
|
@ -5,11 +5,8 @@
|
|||
<input type="hidden" name="_glpi_csrf_token" value="{{ csrf_token_value }}">
|
||||
<input type="hidden" name="_glpi_simple_form" value="1">
|
||||
|
||||
{# 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. #}
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
|
|
@ -25,7 +22,7 @@
|
|||
</div>
|
||||
|
||||
<div class="row">
|
||||
{{ 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
|
||||
}) }}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,20 @@
|
|||
{% import 'components/form/fields_macros.html.twig' as fields %}
|
||||
|
||||
<div class="alert alert-success mt-3 d-flex align-items-center">
|
||||
<i class="ti ti-link me-2"></i>
|
||||
{{ __('Projeto vinculado à issue Redmine de ID', 'redmine') }} <strong class="ms-1">#{{ redmine_project_id }}</strong>
|
||||
<div class="alert alert-success mt-3 d-flex justify-content-between align-items-center">
|
||||
<span>
|
||||
<i class="ti ti-link me-2"></i>
|
||||
{{ __('Vinculado ao projeto Redmine', 'redmine') }}
|
||||
<strong class="ms-1">{{ redmine_project_name|default('') }} (#{{ redmine_project_id }})</strong>
|
||||
</span>
|
||||
<form method="post" action="{{ action_url }}" class="m-0"
|
||||
onsubmit="return confirm('{{ __('Desvincular este projeto do Redmine?', 'redmine') }}');">
|
||||
<input type="hidden" name="_glpi_csrf_token" value="{{ csrf_token_value }}">
|
||||
<input type="hidden" name="{{ owner_field }}" value="{{ owner_id }}">
|
||||
<input type="hidden" name="action" value="unlink">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">
|
||||
<i class="ti ti-unlink me-1"></i>{{ __('Desvincular', 'redmine') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{{ action_url }}" class="mt-2">
|
||||
|
|
|
|||
Loading…
Reference in a new issue