diff --git a/doc/REST-CalDAV-CardDAV/Mail.md b/doc/REST-CalDAV-CardDAV/Mail.md index f4fb4cf886..582350db03 100644 --- a/doc/REST-CalDAV-CardDAV/Mail.md +++ b/doc/REST-CalDAV-CardDAV/Mail.md @@ -9,9 +9,9 @@ Implemented requests (relative to https://example.org/egroupware/groupdav.php) Example: Querying available identities / signatures ```bash -curl -i https://example.org/egroupware/mail --user -H 'Accept: application/json' +curl -i https://example.org/egroupware/groupdav.php/mail --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 ``` -- ```POST /mail[/]``` send mail for default or given account +- ```POST /mail[/]``` send mail for default or given identity
Example: Sending mail @@ -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 \ +``` +curl -i https://example.org/egroupware/groupdav.php/mail/ --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"} ```
@@ -79,21 +85,7 @@ Content-Type: application/json { "status": 200, - "message": "Request to open compose window sent", - "extra": { - "preset": { - "to": [ - "Birgit Becker This is a test :)\n\nRegards", - "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/ --user \ +curl -i https://example.org/egroupware/groupdav.php/mail/attachment/ --user \ --data-binary @ -H 'Content-Type: ' HTTP/1.1 204 No Content Location: https://example.org/egroupware/mail/attachment/ diff --git a/mail/inc/class.mail_compose.inc.php b/mail/inc/class.mail_compose.inc.php index 456fda00f0..4b4faec804 100644 --- a/mail/inc/class.mail_compose.inc.php +++ b/mail/inc/class.mail_compose.inc.php @@ -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. diff --git a/mail/src/ApiHandler.php b/mail/src/ApiHandler.php index 2c8d648b25..24bace43b7 100644 --- a/mail/src/ApiHandler.php +++ b/mail/src/ApiHandler.php @@ -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/" / 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)