false, 'stage' => 'input', 'message' => 'Operação inválida.']); } $json = (string) ($_POST['template_json'] ?? ''); if ($json !== '' && json_decode($json, true) === null && json_decode(stripslashes($json), true) !== null) { $json = stripslashes($json); } if ($json === '') { rm_out(['success' => false, 'stage' => 'input', 'message' => 'Template vazio — gere ou edite o template antes de testar.']); } // ---- render com dados de exemplo (sempre) ---- $ctx = PluginRedmineTemplateengine::sampleContext($op); try { $payload = PluginRedmineTemplateengine::render($json, $ctx); } catch (\Throwable $e) { rm_out(['success' => false, 'stage' => 'render', 'message' => 'Erro no render: ' . $e->getMessage()]); } if ($act === 'preview') { rm_out([ 'success' => true, 'stage' => 'preview', 'payload' => $payload, 'note' => 'Renderizado com DADOS DE EXEMPLO (chamado #4321, tarefa de 1.5h...) + sua configuração real.', ]); } // ---- teste real (dry-run com criação + exclusão) ---- $target = (int) ($_POST['target_project'] ?? 0); $hints = function (array $errors): array { $out = []; foreach ($errors as $e) { $e = (string) $e; if (mb_stripos($e, 'não pode ficar vazio') !== false || mb_stripos($e, "can't be blank") !== false) { $out[] = "$e → inclua/mapeie esse campo no template (veja \"Campos que o Redmine espera\")."; } elseif (mb_stripos($e, 'não está incluso na lista') !== false || mb_stripos($e, 'not included in the list') !== false) { $out[] = "$e → valor não aceito: confira os valores possíveis no dicionário e se está habilitado no projeto alvo."; } elseif (mb_stripos($e, 'não é válido') !== false || mb_stripos($e, 'is invalid') !== false) { $out[] = "$e → formato inválido (tipo errado? identificador com maiúsculas?)."; } else { $out[] = $e; } } return $out; }; $reject = function (array $r) use ($hints, $payload) { return [ 'success' => false, 'stage' => 'dryrun', 'message' => "O Redmine RECUSOU o payload (HTTP {$r['code']}).", 'errors' => $hints($r['body']['errors'] ?? [($r['raw'] !== '' ? $r['raw'] : 'sem resposta da API')]), 'sent' => $payload, ]; }; $result = ['success' => false, 'stage' => 'dryrun', 'message' => 'Nada executado.']; $cleanup = []; switch ($op) { case 'create_project': // nome/identifier de teste para não colidir nem confundir $payload['project']['name'] = '[TESTE TEMPLATE] ' . ($payload['project']['name'] ?? 'teste'); $payload['project']['identifier'] = 'glpi-tpl-test-' . time(); $r = PluginRedmineRedmineapi::rawRequest('POST', '/projects.json', $payload); if ($r['code'] === 201 && isset($r['body']['project']['id'])) { $pid = (int) $r['body']['project']['id']; $del = PluginRedmineRedmineapi::rawRequest('DELETE', "/projects/{$pid}.json"); $result = ['success' => true, 'stage' => 'dryrun', 'sent' => $payload, 'message' => "O Redmine ACEITOU o payload ✔ (projeto de teste #{$pid} criado e apagado — DELETE {$del['code']})."]; } else { $result = $reject($r); } break; case 'create_issue': if ($target <= 0) { rm_out(['success' => false, 'stage' => 'input', 'message' => 'Escolha o projeto alvo para o teste real.']); } $payload['issue']['project_id'] = $target; $payload['issue']['subject'] = '[TESTE TEMPLATE] ' . ($payload['issue']['subject'] ?? 'teste'); $r = PluginRedmineRedmineapi::rawRequest('POST', '/issues.json', $payload); if ($r['code'] === 201 && isset($r['body']['issue']['id'])) { $iid = (int) $r['body']['issue']['id']; $del = PluginRedmineRedmineapi::rawRequest('DELETE', "/issues/{$iid}.json"); $result = ['success' => true, 'stage' => 'dryrun', 'sent' => $payload, 'message' => "O Redmine ACEITOU o payload ✔ (issue de teste #{$iid} criada e apagada — DELETE {$del['code']})."]; } else { $result = $reject($r); } break; case 'log_time': case 'update_status': if ($target <= 0) { rm_out(['success' => false, 'stage' => 'input', 'message' => 'Escolha o projeto alvo para o teste real.']); } // issue temporária no projeto alvo $tmp = PluginRedmineRedmineapi::rawRequest('POST', '/issues.json', [ 'issue' => ['project_id' => $target, 'subject' => '[TESTE TEMPLATE] alvo temporário'], ]); if ($tmp['code'] !== 201 || !isset($tmp['body']['issue']['id'])) { $result = ['success' => false, 'stage' => 'dryrun', 'message' => "Não consegui criar a issue temporária no projeto alvo (HTTP {$tmp['code']}).", 'errors' => $hints($tmp['body']['errors'] ?? [])]; break; } $iid = (int) $tmp['body']['issue']['id']; $cleanup[] = "/issues/{$iid}.json"; if ($op === 'log_time') { $payload['time_entry']['issue_id'] = $iid; $r = PluginRedmineRedmineapi::rawRequest('POST', '/time_entries.json', $payload); $ok = ($r['code'] === 201); } else { $r = PluginRedmineRedmineapi::rawRequest('PUT', "/issues/{$iid}.json", $payload); $ok = ($r['code'] >= 200 && $r['code'] < 300); } $result = $ok ? ['success' => true, 'stage' => 'dryrun', 'sent' => $payload, 'message' => "O Redmine ACEITOU o payload ✔ (testado na issue temporária #{$iid}, apagada em seguida)."] : $reject($r); break; } // limpeza garantida antes de responder foreach ($cleanup as $path) { PluginRedmineRedmineapi::rawRequest('DELETE', $path); } rm_out($result);