mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-27 10:23:28 +01:00
WIP REST API for mail: non-interactive direct sending of mails
This commit is contained in:
parent
2858a8a599
commit
3f760e6e72
@ -9,9 +9,9 @@ Implemented requests (relative to https://example.org/egroupware/groupdav.php)
|
||||
<summary>Example: Querying available identities / signatures</summary>
|
||||
|
||||
```bash
|
||||
curl -i https://example.org/egroupware/mail --user <user> -H 'Accept: application/json'
|
||||
curl -i https://example.org/egroupware/groupdav.php/mail --user <user> -H 'Accept: application/json'
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json; charset=utf-8
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"responses": {
|
||||
@ -23,7 +23,7 @@ Content-Type: application/json; charset=utf-8
|
||||
```
|
||||
</details>
|
||||
|
||||
- ```POST /mail[/<id>]``` send mail for default or given account <id>
|
||||
- ```POST /mail[/<id>]``` send mail for default or given identity <id>
|
||||
<details>
|
||||
<summary>Example: Sending mail</summary>
|
||||
|
||||
@ -31,7 +31,7 @@ The content of the POST request is a JSON encoded object with following attribut
|
||||
- ```to```: array of strings with (RFC882) email addresses like ```["info@egroupware.org", "Ralf Becker <rb@egroupware.org"]```
|
||||
- ```cc```: array of strings with (RFC882) email addresses (optional)
|
||||
- ```bcc```: array of strings with (RFC882) email addresses (optional)
|
||||
- ```replyTo```: string with (RFC822) email address (optional)
|
||||
- ```replyto```: string with (RFC822) email address (optional)
|
||||
- ```subject```: string with subject
|
||||
- ```body```: string plain text body (optional)
|
||||
- ```bodyHtml```: string with html body (optional)
|
||||
@ -44,12 +44,19 @@ The content of the POST request is a JSON encoded object with following attribut
|
||||
- ```shareExpiration```: "yyyy-mm-dd", default not accessed in 100 days (EPL only)
|
||||
- ```sharePassword```: string with password required to access share, default none (EPL only)
|
||||
- ```folder```: folder to store send mail, default Sent folder
|
||||
- ```priority```: 1: high, 3: normal (default), 5: low
|
||||
|
||||
```bash
|
||||
curl -i https://example.org/egroupware/mail --user <user> \
|
||||
```
|
||||
curl -i https://example.org/egroupware/groupdav.php/mail/ --user <user> \
|
||||
-X POST -H 'Content-Type: application/json' \
|
||||
--content `{"to":["info@egroupware.org"],"subject":"Testmail","body":"This is a test :)\n\nRegards"}`
|
||||
HTTP/1.1 204 No Content
|
||||
--data-binary '{"to":["info@egroupware.org"],"subject":"Testmail","body":"This is a test :)\n\nRegards"}'
|
||||
HTTP/1.1 200 Ok
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"status": 200,
|
||||
"message": "Mail successful sent"
|
||||
}
|
||||
```
|
||||
If you are not authenticated you will get:
|
||||
```
|
||||
@ -61,9 +68,8 @@ If there is an error sending the mail you will get:
|
||||
```
|
||||
HTTP/1.1 500 Internal Server Error
|
||||
Content-Type: application/json
|
||||
Content-Length: ...
|
||||
|
||||
{"error": 123,"message":"SMTP Server not reachable"}
|
||||
{"error": 500,"message":"SMTP Server not reachable"}
|
||||
```
|
||||
</details>
|
||||
|
||||
@ -79,21 +85,7 @@ Content-Type: application/json
|
||||
|
||||
{
|
||||
"status": 200,
|
||||
"message": "Request to open compose window sent",
|
||||
"extra": {
|
||||
"preset": {
|
||||
"to": [
|
||||
"Birgit Becker <bb@egroupware.org"
|
||||
],
|
||||
"cc": [
|
||||
"info@egroupware.org"
|
||||
],
|
||||
"subject": "Testmail",
|
||||
"body": "<pre>This is a test :)\n\nRegards</pre>",
|
||||
"mimeType": "html",
|
||||
"identity": "52"
|
||||
}
|
||||
}
|
||||
"message": "Request to open compose window sent"
|
||||
}
|
||||
```
|
||||
- user is not online, therefore compose window can NOT be opened
|
||||
@ -116,7 +108,7 @@ The content of the POST request is the attachment, a Location header in the resp
|
||||
to use in further requests, instead of the attachment.
|
||||
|
||||
```
|
||||
curl -i https://example.org/egroupware/mail/attachment/<filename> --user <user> \
|
||||
curl -i https://example.org/egroupware/groupdav.php/mail/attachment/<filename> --user <user> \
|
||||
--data-binary @<file> -H 'Content-Type: <content-type-of-file>'
|
||||
HTTP/1.1 204 No Content
|
||||
Location: https://example.org/egroupware/mail/attachment/<token>
|
||||
|
@ -1006,7 +1006,7 @@ class mail_compose
|
||||
}
|
||||
if(!empty($remember)) $content = array_merge($content,$remember);
|
||||
}
|
||||
foreach(array('to','cc','bcc','subject','body','mimeType') as $name)
|
||||
foreach(array('to','cc','bcc','subject','body','mimeType','replyto','priority') as $name)
|
||||
{
|
||||
//always handle mimeType
|
||||
if ($name=='mimeType' && !empty($_REQUEST['preset'][$name]))
|
||||
@ -2504,7 +2504,7 @@ class mail_compose
|
||||
$disableRuler = false;
|
||||
$signature = $_identity['ident_signature'];
|
||||
$sigAlreadyThere = $this->mailPreferences['insertSignatureAtTopOfMessage']!='no_belowaftersend'?1:0;
|
||||
if ($sigAlreadyThere)
|
||||
if ($sigAlreadyThere && empty($_formData['add_signature']))
|
||||
{
|
||||
// note: if you use stationery ' s the insert signatures at the top does not apply here anymore, as the signature
|
||||
// is already part of the body, so the signature part of the template will not be applied.
|
||||
|
@ -58,31 +58,24 @@ class ApiHandler extends Api\CalDAV\Handler
|
||||
try {
|
||||
if (str_starts_with($path, '/mail/attachments/'))
|
||||
{
|
||||
$attachment_path = tempnam($GLOBALS['egw_info']['server']['temp_dir'], 'attach--'.
|
||||
(str_replace('/', '-', substr($options['path'], 18)) ?: 'no-name').'--');
|
||||
if (file_put_contents($attachment_path, $options['content']))
|
||||
{
|
||||
header('Location: '.($location = '/mail/attachments/'.substr(basename($attachment_path), 8)));
|
||||
echo json_encode([
|
||||
'status' => 200,
|
||||
'message' => 'Attachment stored',
|
||||
'location' => $location,
|
||||
], self::JSON_RESPONSE_OPTIONS);
|
||||
return '200 Ok';
|
||||
}
|
||||
throw new \Exception('Error storing attachment');
|
||||
return self::storeAttachment($path, $options['content']);
|
||||
}
|
||||
elseif (preg_match('#^/mail/((\d+):(\d+)/)?(compose)?#', $path, $matches))
|
||||
elseif (preg_match('#^/mail(/(\d+))?(/compose)?#', $path, $matches))
|
||||
{
|
||||
$acc_id = $matches[2] ?? null;
|
||||
$ident_id = $matches[3] ?? null;
|
||||
$do_compose = (bool)($matches[4] ?? false);
|
||||
$ident_id = $matches[2] ?? self::defaultIdentity($user);
|
||||
$do_compose = (bool)($matches[3] ?? false);
|
||||
if (!($data = json_decode($options['content'], true)))
|
||||
{
|
||||
throw new \Exception('Error decoding JSON: '.json_last_error_msg(), 422);
|
||||
}
|
||||
// ToDo: check required attributes
|
||||
|
||||
$preset = array_filter(array_intersect_key($data, array_flip(['to', 'cc', 'bcc', 'replyto', 'subject', 'priority']))+[
|
||||
'body' => $data['bodyHtml'] ?? null ?: $data['body'] ?? '',
|
||||
'mimeType' => !empty($data['bodyHtml']) ? 'html' : 'plain',
|
||||
'identity' => $ident_id,
|
||||
]+self::prepareAttachments($data['attachments'] ?? [], $data['attachmentType'] ?? 'attach', $do_compose));
|
||||
|
||||
// for compose we need to construct a URL and push it to the client (or give an error if the client is not online)
|
||||
if ($do_compose)
|
||||
{
|
||||
@ -91,39 +84,38 @@ class ApiHandler extends Api\CalDAV\Handler
|
||||
$account_lid = Api\Accounts::id2name($user);
|
||||
throw new \Exception("User '$account_lid' (#$user) is NOT online", 404);
|
||||
}
|
||||
$extra = [
|
||||
//'menuaction' => 'mail.mail_compose.compose',
|
||||
'preset' => array_filter(array_intersect_key($data, array_flip(['to', 'cc', 'bcc', 'subject']))+[
|
||||
'body' => $data['bodyHtml'] ?? null ?: $data['body'] ?? '',
|
||||
'mimeType' => !empty($data['bodyHtml']) ? 'html' : 'plain',
|
||||
'identity' => $ident_id,
|
||||
]+self::prepareAttachments($data['attachments'] ?? [], $data['attachmentType'] ?? 'attach')),
|
||||
];
|
||||
$push = new Api\Json\Push($user);
|
||||
//$push->call('egw.open_link', $link, '_blank', '640x1024');
|
||||
$push->call('egw.open', '', 'mail', 'add', $extra, '_blank', 'mail');
|
||||
$push->call('egw.open', '', 'mail', 'add', ['preset' => $preset], '_blank', 'mail');
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'status' => 200,
|
||||
'message' => 'Request to open compose window sent',
|
||||
'extra' => $extra,
|
||||
//'data' => $preset,
|
||||
], self::JSON_RESPONSE_OPTIONS);
|
||||
return true;
|
||||
}
|
||||
|
||||
$compose = new mail_compose($acc_id);
|
||||
$compose->compose([
|
||||
'mailaccount' => $acc_id.':'.$ident_id,
|
||||
'mail_plaintext' => $data['body'] ?? null,
|
||||
'mail_htmltext' => $data['bodyHtml'] ?? null,
|
||||
'mimeType' => !empty($data['bodyHtml']) ? 'html' : 'plain',
|
||||
'file' => array_map(__CLASS__.'::uploadsForAttachments', $data['attachments'] ?? []),
|
||||
]+array_diff_key($data+array_flip(['attachments', 'body', 'bodyHtml'])));
|
||||
$compose = new \mail_compose($acc_id=Api\Mail\Account::read_identity($ident_id)['acc_id']);
|
||||
$preset = array_filter([
|
||||
'mailaccount' => $acc_id,
|
||||
'mailidentity' => $ident_id,
|
||||
'identity' => null,
|
||||
'add_signature' => true, // add signature in send, independent what preference says
|
||||
]+$preset);
|
||||
if ($compose->send($preset))
|
||||
{
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'status' => 200,
|
||||
'message' => 'Mail successful sent',
|
||||
//'data' => $preset,
|
||||
], self::JSON_RESPONSE_OPTIONS);
|
||||
return true;
|
||||
}
|
||||
throw new \Exception($compose->error_info);
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo $options['content'];
|
||||
return true;
|
||||
throw new \Exception('Not Found', 404);
|
||||
}
|
||||
catch (\Throwable $e) {
|
||||
_egw_log_exception($e);
|
||||
@ -142,15 +134,64 @@ class ApiHandler extends Api\CalDAV\Handler
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store uploaded attachment and return token
|
||||
*
|
||||
* @param string $path
|
||||
* @param string|stream $content
|
||||
* @return string HTTP status
|
||||
* @throws \Exception on error
|
||||
*/
|
||||
protected static function storeAttachment(string $path, $content)
|
||||
{
|
||||
$attachment_path = tempnam($GLOBALS['egw_info']['server']['temp_dir'], 'attach--'.
|
||||
(str_replace('/', '-', substr($path, 18)) ?: 'no-name').'--');
|
||||
if (file_put_contents($attachment_path, $content))
|
||||
{
|
||||
header('Location: '.($location = '/mail/attachments/'.substr(basename($attachment_path), 8)));
|
||||
echo json_encode([
|
||||
'status' => 200,
|
||||
'message' => 'Attachment stored',
|
||||
'location' => $location,
|
||||
], self::JSON_RESPONSE_OPTIONS);
|
||||
return '200 Ok';
|
||||
}
|
||||
throw new \Exception('Error storing attachment');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default identity of user
|
||||
*
|
||||
* @param int $user
|
||||
* @return int ident_id
|
||||
* @throws Api\Exception\WrongParameter
|
||||
* @throws \Exception (404) if user has no IMAP account
|
||||
*/
|
||||
protected static function defaultIdentity(int $user)
|
||||
{
|
||||
foreach(Api\Mail\Account::search($user,false) as $acc_id => $account)
|
||||
{
|
||||
// do NOT add SMTP only accounts as identities
|
||||
if (!$account->is_imap(false)) continue;
|
||||
|
||||
foreach($account->identities($acc_id) as $ident_id => $identity)
|
||||
{
|
||||
return $ident_id;
|
||||
}
|
||||
}
|
||||
throw new \Exception("No IMAP account found for user #$user", 404);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an attachment name into an upload array for mail_compose::compose
|
||||
*
|
||||
* @param string[]? $attachments either "/mail/attachments/<token>" / file in temp_dir or VFS path
|
||||
* @param string? $attachmentType "attach" (default), "link", "share_ro", "share_rw"
|
||||
* @param bool $compose true: for compose window, false: to send
|
||||
* @return array with values for keys "file", "name" and "filemode"
|
||||
* @throws Exception if file not found or unreadable
|
||||
*/
|
||||
protected static function prepareAttachments(array $attachments, string $attachmentType=null)
|
||||
protected static function prepareAttachments(array $attachments, string $attachmentType=null, bool $compose=true)
|
||||
{
|
||||
$ret = [];
|
||||
foreach($attachments as $attachment)
|
||||
@ -161,14 +202,20 @@ class ApiHandler extends Api\CalDAV\Handler
|
||||
{
|
||||
throw new \Exception("Attachment $attachment NOT found", 400);
|
||||
}
|
||||
$ret['file'][] = $path;
|
||||
$ret['name'][] = $matches[2];
|
||||
/*return [
|
||||
'name' => $matches[2],
|
||||
'type' => Api\Vfs::mime_content_type($path),
|
||||
'file' => $path,
|
||||
'size' => filesize($path),
|
||||
];*/
|
||||
if ($compose)
|
||||
{
|
||||
$ret['file'][] = $path;
|
||||
$ret['name'][] = $matches[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
$ret['attachments'][] = [
|
||||
'name' => $matches[2],
|
||||
'type' => Api\Vfs::mime_content_type($path),
|
||||
'file' => $path,
|
||||
'size' => filesize($path),
|
||||
];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -176,14 +223,20 @@ class ApiHandler extends Api\CalDAV\Handler
|
||||
{
|
||||
throw new \Exception("Attachment $attachment NOT found", 400);
|
||||
}
|
||||
$ret['file'][] = Api\Vfs::PREFIX.$attachment;
|
||||
$ret['name'][] = Api\Vfs::basename($attachment);
|
||||
/*return [
|
||||
'name' => Api\Vfs::basename($attachment),
|
||||
'type' => Api\Vfs::mime_content_type($attachment),
|
||||
'file' => Api\Vfs::PREFIX.$attachment,
|
||||
'size' => filesize(Api\Vfs::PREFIX.$attachment),
|
||||
];*/
|
||||
if ($compose)
|
||||
{
|
||||
$ret['file'][] = Api\Vfs::PREFIX.$attachment;
|
||||
$ret['name'][] = Api\Vfs::basename($attachment);
|
||||
}
|
||||
else
|
||||
{
|
||||
$ret['attachments'][] = [
|
||||
'name' => Api\Vfs::basename($attachment),
|
||||
'type' => Api\Vfs::mime_content_type($attachment),
|
||||
'file' => Api\Vfs::PREFIX.$attachment,
|
||||
'size' => filesize(Api\Vfs::PREFIX.$attachment),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($ret)
|
||||
|
Loading…
Reference in New Issue
Block a user