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/)
|
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/).
|
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]
|
## [1.5.2]
|
||||||
|
|
||||||
### Corrigido
|
### Corrigido
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,10 @@ if (isset($_POST["action"])) {
|
||||||
]);
|
]);
|
||||||
Session::addMessageAfterRedirect(__('Categoria padrão salva com sucesso!', 'redmine'));
|
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') {
|
elseif ($action === 'process') {
|
||||||
$redmine_project_id = $_POST['redmine_project_id'] ?? '';
|
$redmine_project_id = $_POST['redmine_project_id'] ?? '';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,11 @@ if (isset($_POST["action"])) {
|
||||||
]);
|
]);
|
||||||
Session::addMessageAfterRedirect("Categoria padrão salva com sucesso!");
|
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') {
|
elseif ($action === 'process') {
|
||||||
$redmine_project_id = $_POST['redmine_project_id'] ?? '';
|
$redmine_project_id = $_POST['redmine_project_id'] ?? '';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,9 @@ class PluginRedmineEntity extends CommonDBTM {
|
||||||
$entities_id = $entity->getID();
|
$entities_id = $entity->getID();
|
||||||
$form_url = $CFG_GLPI['root_doc'] . '/plugins/redmine/front/entity.form.php';
|
$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?
|
// Already linked?
|
||||||
$iterator = $DB->request([
|
$iterator = $DB->request([
|
||||||
'FROM' => 'glpi_plugin_redmine_entities',
|
'FROM' => 'glpi_plugin_redmine_entities',
|
||||||
|
|
@ -56,6 +59,7 @@ class PluginRedmineEntity extends CommonDBTM {
|
||||||
'owner_field' => 'entities_id',
|
'owner_field' => 'entities_id',
|
||||||
'owner_id' => $entities_id,
|
'owner_id' => $entities_id,
|
||||||
'redmine_project_id' => $redmineProjectId,
|
'redmine_project_id' => $redmineProjectId,
|
||||||
|
'redmine_project_name' => $project_options[$redmineProjectId] ?? '',
|
||||||
'cat_options' => $cat_options,
|
'cat_options' => $cat_options,
|
||||||
'default_category_id' => (int) ($link['default_category_id'] ?? 0),
|
'default_category_id' => (int) ($link['default_category_id'] ?? 0),
|
||||||
'csrf_token_value' => Session::getNewCSRFToken(),
|
'csrf_token_value' => Session::getNewCSRFToken(),
|
||||||
|
|
@ -64,35 +68,17 @@ class PluginRedmineEntity extends CommonDBTM {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not linked: show the link/create form (reuses the project tab template).
|
// 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);
|
$description = \Glpi\RichText\RichText::getTextFromHtml($entity->fields['comment'] ?? '', false, true, true);
|
||||||
|
|
||||||
\Glpi\Application\View\TemplateRenderer::getInstance()->display('@redmine/project_form.html.twig', [
|
\Glpi\Application\View\TemplateRenderer::getInstance()->display('@redmine/project_form.html.twig', [
|
||||||
'action_url' => $form_url,
|
'action_url' => $form_url,
|
||||||
'owner_field' => 'entities_id',
|
'owner_field' => 'entities_id',
|
||||||
'owner_id' => $entities_id,
|
'owner_id' => $entities_id,
|
||||||
'redmine_projects' => $redmine_projects,
|
'project_options' => PluginRedmineProject::getRedmineProjectOptions(true),
|
||||||
'project_name' => $entity->fields['name'],
|
'project_name' => $entity->fields['name'],
|
||||||
'project_description' => $description,
|
'project_description' => $description,
|
||||||
'project_identifier' => 'glpi-ent-' . $entities_id,
|
'project_identifier' => 'glpi-ent-' . $entities_id,
|
||||||
'modules' => $modules,
|
'modules' => PluginRedmineProject::getModules(),
|
||||||
'csrf_token_value' => Session::getNewCSRFToken()
|
'csrf_token_value' => Session::getNewCSRFToken()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,13 @@ class PluginRedmineProject extends CommonDBTM {
|
||||||
global $DB, $CFG_GLPI;
|
global $DB, $CFG_GLPI;
|
||||||
|
|
||||||
$projects_id = $project->getID();
|
$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([
|
$iterator = $DB->request([
|
||||||
'FROM' => 'glpi_plugin_redmine_projects',
|
'FROM' => 'glpi_plugin_redmine_projects',
|
||||||
'WHERE' => ['projects_id' => $projects_id]
|
'WHERE' => ['projects_id' => $projects_id]
|
||||||
|
|
@ -43,7 +48,6 @@ class PluginRedmineProject extends CommonDBTM {
|
||||||
$link = $iterator->current();
|
$link = $iterator->current();
|
||||||
$redmineProjectId = (int) $link['redmine_project_id'];
|
$redmineProjectId = (int) $link['redmine_project_id'];
|
||||||
|
|
||||||
// Issue categories are per Redmine project.
|
|
||||||
$cat_options = [];
|
$cat_options = [];
|
||||||
foreach (PluginRedmineRedmineapi::getProjectCategories($redmineProjectId) as $c) {
|
foreach (PluginRedmineRedmineapi::getProjectCategories($redmineProjectId) as $c) {
|
||||||
if (isset($c['id'], $c['name'])) {
|
if (isset($c['id'], $c['name'])) {
|
||||||
|
|
@ -52,10 +56,11 @@ class PluginRedmineProject extends CommonDBTM {
|
||||||
}
|
}
|
||||||
|
|
||||||
\Glpi\Application\View\TemplateRenderer::getInstance()->display('@redmine/project_linked.html.twig', [
|
\Glpi\Application\View\TemplateRenderer::getInstance()->display('@redmine/project_linked.html.twig', [
|
||||||
'action_url' => $CFG_GLPI['root_doc'] . '/plugins/redmine/front/project.form.php',
|
'action_url' => $form_url,
|
||||||
'owner_field' => 'projects_id',
|
'owner_field' => 'projects_id',
|
||||||
'owner_id' => $projects_id,
|
'owner_id' => $projects_id,
|
||||||
'redmine_project_id' => $redmineProjectId,
|
'redmine_project_id' => $redmineProjectId,
|
||||||
|
'redmine_project_name' => $project_options[$redmineProjectId] ?? '',
|
||||||
'cat_options' => $cat_options,
|
'cat_options' => $cat_options,
|
||||||
'default_category_id' => (int) ($link['default_category_id'] ?? 0),
|
'default_category_id' => (int) ($link['default_category_id'] ?? 0),
|
||||||
'csrf_token_value' => Session::getNewCSRFToken(),
|
'csrf_token_value' => Session::getNewCSRFToken(),
|
||||||
|
|
@ -63,17 +68,51 @@ class PluginRedmineProject extends CommonDBTM {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch Redmine Metadata Cache
|
$description = \Glpi\RichText\RichText::getTextFromHtml($project->fields['content'], false, true, true);
|
||||||
$config_it = $DB->request(['FROM' => 'glpi_plugin_redmine_configs', 'WHERE' => ['id' => 1]]);
|
|
||||||
$config = count($config_it) > 0 ? $config_it->current() : [];
|
\Glpi\Application\View\TemplateRenderer::getInstance()->display('@redmine/project_form.html.twig', [
|
||||||
$metadata = [];
|
'action_url' => $form_url,
|
||||||
if (!empty($config['metadata_cache'])) {
|
'owner_field' => 'projects_id',
|
||||||
$metadata = json_decode($config['metadata_cache'], true);
|
'owner_id' => $projects_id,
|
||||||
|
'project_options' => self::getRedmineProjectOptions(true),
|
||||||
|
'project_name' => $project->fields['name'],
|
||||||
|
'project_description' => $description,
|
||||||
|
'project_identifier' => 'glpi-proj-' . $projects_id,
|
||||||
|
'modules' => self::getModules(),
|
||||||
|
'csrf_token_value' => Session::getNewCSRFToken()
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$redmine_projects = $metadata['projects'] ?? [];
|
/**
|
||||||
|
* {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;
|
||||||
|
}
|
||||||
|
|
||||||
$modules = [
|
/**
|
||||||
|
* Redmine project modules offered on creation.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
static function getModules() {
|
||||||
|
return [
|
||||||
'issue_tracking' => 'Gerenciamento de Tarefas',
|
'issue_tracking' => 'Gerenciamento de Tarefas',
|
||||||
'time_tracking' => 'Gerenciamento de tempo',
|
'time_tracking' => 'Gerenciamento de tempo',
|
||||||
'news' => 'Notícias',
|
'news' => 'Notícias',
|
||||||
|
|
@ -85,22 +124,5 @@ class PluginRedmineProject extends CommonDBTM {
|
||||||
'calendar' => 'Calendário',
|
'calendar' => 'Calendário',
|
||||||
'gantt' => 'Gantt'
|
'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'],
|
|
||||||
'project_description' => $description,
|
|
||||||
'project_identifier' => 'glpi-proj-' . $projects_id,
|
|
||||||
'modules' => $modules,
|
|
||||||
'csrf_token_value' => Session::getNewCSRFToken()
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?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.
|
* 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_csrf_token" value="{{ csrf_token_value }}">
|
||||||
<input type="hidden" name="_glpi_simple_form" value="1">
|
<input type="hidden" name="_glpi_simple_form" value="1">
|
||||||
|
|
||||||
{# Build a {id: name} options map from the cached Redmine projects #}
|
{# project_options ({id: name}) é montado no PHP — não usar merge no Twig,
|
||||||
{% set rp_options = {} %}
|
que renumera chaves inteiras e corrompe os valores das opções. #}
|
||||||
{% for rp in redmine_projects %}
|
|
||||||
{% set rp_options = rp_options|merge({(rp.id): rp.name}) %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
|
@ -25,7 +22,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<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,
|
'display_emptychoice': true,
|
||||||
'full_width': true
|
'full_width': true
|
||||||
}) }}
|
}) }}
|
||||||
|
|
@ -51,7 +48,7 @@
|
||||||
'helper': __('Projetos públicos são visíveis para todos.', 'redmine')
|
'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,
|
'display_emptychoice': true,
|
||||||
'full_width': true
|
'full_width': true
|
||||||
}) }}
|
}) }}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,20 @@
|
||||||
{% import 'components/form/fields_macros.html.twig' as fields %}
|
{% import 'components/form/fields_macros.html.twig' as fields %}
|
||||||
|
|
||||||
<div class="alert alert-success mt-3 d-flex align-items-center">
|
<div class="alert alert-success mt-3 d-flex justify-content-between align-items-center">
|
||||||
|
<span>
|
||||||
<i class="ti ti-link me-2"></i>
|
<i class="ti ti-link me-2"></i>
|
||||||
{{ __('Projeto vinculado à issue Redmine de ID', 'redmine') }} <strong class="ms-1">#{{ redmine_project_id }}</strong>
|
{{ __('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>
|
</div>
|
||||||
|
|
||||||
<form method="post" action="{{ action_url }}" class="mt-2">
|
<form method="post" action="{{ action_url }}" class="mt-2">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue