Make Helpdesk email updates explicit
This commit is contained in:
@@ -101,17 +101,41 @@ final class RedmineClient
|
||||
/**
|
||||
* Update a Redmine issue.
|
||||
*
|
||||
* By default this uses the normal Redmine issue REST API and does not send
|
||||
* a Helpdesk email. To send a customer-visible Helpdesk response, pass
|
||||
* ['send_helpdesk_email' => true] with a notes field, or call
|
||||
* sendHelpdeskIssueResponse() directly.
|
||||
*
|
||||
* Typical fields include notes, subject, status_id, priority_id,
|
||||
* assigned_to_id, private_notes, due_date, and tracker_id.
|
||||
*
|
||||
* @param array<string,mixed> $fields
|
||||
*/
|
||||
public function updateIssue(int $issueId, array $fields): bool
|
||||
public function updateIssue(int $issueId, array $fields, array $options = []): bool
|
||||
{
|
||||
if ($fields === []) {
|
||||
throw new RuntimeException('Updating an issue requires at least one field.');
|
||||
}
|
||||
|
||||
if (!empty($options['send_helpdesk_email'])) {
|
||||
if (!isset($fields['notes']) || trim((string) $fields['notes']) === '') {
|
||||
throw new RuntimeException('Sending a Helpdesk email requires a non-empty notes field.');
|
||||
}
|
||||
if (!empty($fields['private_notes'])) {
|
||||
throw new RuntimeException('A private note cannot be sent as a Helpdesk email.');
|
||||
}
|
||||
|
||||
$issueFields = $fields;
|
||||
unset($issueFields['notes'], $issueFields['private_notes']);
|
||||
if ($issueFields !== []) {
|
||||
$this->updateIssue($issueId, $issueFields);
|
||||
}
|
||||
|
||||
$this->sendHelpdeskIssueResponse($issueId, (string) $fields['notes'], $options);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$issueApi = $this->client->getApi('issue');
|
||||
$issueApi->update($issueId, $fields);
|
||||
$this->assertLastApiResponseSucceeded($issueApi, 'update issue #' . $issueId);
|
||||
@@ -119,6 +143,46 @@ final class RedmineClient
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a Helpdesk email response for an existing Helpdesk-backed issue.
|
||||
*
|
||||
* This uses the RedmineUP Helpdesk API endpoint that corresponds to sending
|
||||
* a note with the Helpdesk UI's "Send note" checkbox enabled.
|
||||
*
|
||||
* @param array<string,mixed> $options Optional to_address, cc_address,
|
||||
* bcc_address, and status_id fields.
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function sendHelpdeskIssueResponse(int $issueId, string $content, array $options = []): array
|
||||
{
|
||||
if (trim($content) === '') {
|
||||
throw new RuntimeException('Sending a Helpdesk response requires non-empty content.');
|
||||
}
|
||||
|
||||
$message = [
|
||||
'issue_id' => $issueId,
|
||||
'content' => $content,
|
||||
];
|
||||
if (isset($options['status_id'])) {
|
||||
$message['status_id'] = $options['status_id'];
|
||||
}
|
||||
|
||||
$payload = ['message' => $message];
|
||||
foreach (['to_address', 'cc_address', 'bcc_address'] as $key) {
|
||||
if (isset($options[$key])) {
|
||||
$payload[$key] = $options[$key];
|
||||
}
|
||||
}
|
||||
|
||||
$response = $this->postJson('/helpdesk/email_note', $payload);
|
||||
if (!isset($response['message']) || !is_array($response['message'])) {
|
||||
throw new RuntimeException('Helpdesk email response returned unexpected JSON.');
|
||||
}
|
||||
|
||||
return $response['message'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a Redmine issue.
|
||||
*/
|
||||
@@ -269,6 +333,47 @@ final class RedmineClient
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $payload
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private function postJson(string $path, array $payload): array
|
||||
{
|
||||
if (!$this->client instanceof HttpClient) {
|
||||
throw new RuntimeException('Configured Redmine client cannot issue raw HTTP requests.');
|
||||
}
|
||||
|
||||
$requestPath = $this->buildPath($path, []);
|
||||
$encoded = json_encode($payload);
|
||||
if ($encoded === false) {
|
||||
throw new RuntimeException('Could not encode Redmine POST payload.');
|
||||
}
|
||||
|
||||
$response = $this->client->request(HttpFactory::makeJsonRequest('POST', $requestPath, $encoded));
|
||||
$status = $response->getStatusCode();
|
||||
if ($status >= 400) {
|
||||
throw new RuntimeException('Redmine POST ' . $requestPath . ' failed with HTTP ' . $status . ': ' . $response->getContent());
|
||||
}
|
||||
|
||||
$body = $response->getContent();
|
||||
if ($body === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
$decoded = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (Throwable $exception) {
|
||||
throw new RuntimeException('Redmine POST ' . $requestPath . ' returned invalid JSON.', 0, $exception);
|
||||
}
|
||||
|
||||
if (!is_array($decoded)) {
|
||||
throw new RuntimeException('Redmine POST ' . $requestPath . ' returned invalid JSON.');
|
||||
}
|
||||
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $params
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user