From 89107387d551829eed10722e3b955865b6d96831 Mon Sep 17 00:00:00 2001 From: djmaze Date: Tue, 13 Apr 2021 14:11:17 +0200 Subject: [PATCH 1/5] Improved #81 --- dev/Common/File.js | 236 ++++++++---------- dev/Model/Attachment.js | 54 +--- dev/Model/ComposeAttachment.js | 9 +- dev/Styles/Attachmnets.less | 10 - .../0.0.0/app/libraries/MailSo/Base/Utils.php | 8 +- .../templates/Views/User/MailMessageView.html | 12 +- .../templates/Views/User/PopupsCompose.html | 3 +- vendors/fontastic/styles.css | 10 +- 8 files changed, 129 insertions(+), 213 deletions(-) diff --git a/dev/Common/File.js b/dev/Common/File.js index beffa01d2..28bc2943f 100644 --- a/dev/Common/File.js +++ b/dev/Common/File.js @@ -11,113 +11,109 @@ const sizes = ['B', 'KiB', 'MiB', 'GiB', 'TiB'], exts = { - eml: 'message/rfc822', - mime: 'message/rfc822', - rtx: 'text/richtext', - vcard: 'text/vcard', - vcf: 'text/vcard', - htm: 'text/html', - html: 'text/html', - csv: 'text/csv', - ics: 'text/calendar', - ifb: 'text/calendar', - xml: 'text/xml', - json: app+'json', - p10: app+'pkcs10', - p7c: app+'pkcs7-mime', - p7m: app+'pkcs7-mime', - p7s: app+'pkcs7-signature', - torrent: app+'x-bittorrent', + eml: 'message/rfc822', + mime: 'message/rfc822', + vcard: 'text/vcard', + vcf: 'text/vcard', + htm: 'text/html', + html: 'text/html', + csv: 'text/csv', + ics: 'text/calendar', + xml: 'text/xml', + json: app+'json', + asc: app+'pgp-signature', + p10: app+'pkcs10', + p7c: app+'pkcs7-mime', + p7m: app+'pkcs7-mime', + p7s: app+'pkcs7-signature', + torrent: app+'x-bittorrent', - // scripts - js: app+'javascript', - pl: 'text/perl', - css: 'text/css', - asp: 'text/asp', - php: app+'x-httpd-php', - phtml: app+'x-httpd-php', + // scripts + js: app+'javascript', + pl: 'text/perl', + css: 'text/css', + asp: 'text/asp', + php: app+'x-php', - // images - jpg: 'image/jpeg', - ico: 'image/x-icon', - tif: 'image/tiff', - svg: 'image/svg+xml', - svgz: 'image/svg+xml', + // images + jpg: 'image/jpeg', + ico: 'image/x-icon', + tif: 'image/tiff', + svg: 'image/svg+xml', + svgz: 'image/svg+xml', - // archives - zip: app+'zip', - '7z': app+'x-7z-compressed', - rar: app+'x-rar-compressed', - cab: app+'vnd.ms-cab-compressed', - gz: app+'x-gzip', - tgz: app+'x-gzip', - bz: app+'x-bzip', - bz2: app+'x-bzip2', - deb: app+'x-debian-package', + // archives + zip: app+'zip', + '7z': app+'x-7z-compressed', + rar: app+'x-rar-compressed', + cab: app+'vnd.ms-cab-compressed', + gz: app+'x-gzip', + tgz: app+'x-gzip', + bz: app+'x-bzip', + bz2: app+'x-bzip2', + deb: app+'x-debian-package', - // audio - mp3: 'audio/mpeg', - wav: 'audio/x-wav', - mp4a: 'audio/mp4', - weba: 'audio/webm', - m3u: 'audio/x-mpegurl', + // audio + mp3: 'audio/mpeg', + wav: 'audio/x-wav', + mp4a: 'audio/mp4', + weba: 'audio/webm', + m3u: 'audio/x-mpegurl', - // video - qt: 'video/quicktime', - mov: 'video/quicktime', - wmv: 'video/windows-media', - avi: 'video/x-msvideo', - '3gp': 'video/3gpp', - '3g2': 'video/3gpp2', - mp4v: 'video/mp4', - mpg4: 'video/mp4', - ogv: 'video/ogg', - m4v: 'video/x-m4v', - asf: 'video/x-ms-asf', - asx: 'video/x-ms-asf', - wm: 'video/x-ms-wm', - wmx: 'video/x-ms-wmx', - wvx: 'video/x-ms-wvx', - movie: 'video/x-sgi-movie', + // video + qt: 'video/quicktime', + mov: 'video/quicktime', + wmv: 'video/windows-media', + avi: 'video/x-msvideo', + '3gp': 'video/3gpp', + '3g2': 'video/3gpp2', + mp4v: 'video/mp4', + mpg4: 'video/mp4', + ogv: 'video/ogg', + m4v: 'video/x-m4v', + asf: 'video/x-ms-asf', + asx: 'video/x-ms-asf', + wm: 'video/x-ms-wm', + wmx: 'video/x-ms-wmx', + wvx: 'video/x-ms-wvx', + movie: 'video/x-sgi-movie', - // adobe - pdf: app+'pdf', - psd: 'image/vnd.adobe.photoshop', - ai: app+'postscript', - eps: app+'postscript', - ps: app+'postscript', + // adobe + pdf: app+'pdf', + psd: 'image/vnd.adobe.photoshop', + ai: app+'postscript', + eps: app+'postscript', + ps: app+'postscript', - // ms office - doc: app+'msword', - rtf: app+'rtf', - xls: app+'vnd.ms-excel', - ppt: app+'vnd.ms-powerpoint', - docx: msOffice+'wordprocessingml.document', - xlsx: msOffice+'spreadsheetml.sheet', - dotx: msOffice+'wordprocessingml.template', - pptx: msOffice+'presentationml.presentation', + // ms office + doc: app+'msword', + rtf: app+'rtf', + xls: app+'vnd.ms-excel', + ppt: app+'vnd.ms-powerpoint', + docx: msOffice+'wordprocessingml.document', + xlsx: msOffice+'spreadsheetml.sheet', + dotx: msOffice+'wordprocessingml.template', + pptx: msOffice+'presentationml.presentation', - // open office - odt: openDoc+'text', - ods: openDoc+'spreadsheet', - odp: openDoc+'presentation' -}; + // open office + odt: openDoc+'text', + ods: openDoc+'spreadsheet', + odp: openDoc+'presentation' + }; export const FileType = { Unknown: 'unknown', Text: 'text', - Html: 'html', Code: 'code', Eml: 'eml', - WordText: 'word-text', + Word: 'word', Pdf: 'pdf', Image: 'image', Audio: 'audio', Video: 'video', - Sheet: 'sheet', + Spreadsheet: 'spreadsheet', Presentation: 'presentation', Certificate: 'certificate', - CertificateBin: 'certificate-bin', Archive: 'archive' }; @@ -138,7 +134,7 @@ export const FileInfo = { return app + 'ms-tnef'; } let ext = fileName.split('.').pop(); - if (/^(txt|text|def|list|in|ini|log|sql|cfg|conf|asc)$/.test(ext)) + if (/^(txt|text|def|list|in|ini|log|sql|cfg|conf)$/.test(ext)) return 'text/plain'; if (/^(mpe?g|mpe|m1v|m2v)$/.test(ext)) return 'video/mpeg'; @@ -186,15 +182,12 @@ export const FileInfo = { case 'video' == mimeTypeParts[0] || 'mkv' == ext || 'avi' == ext: result = FileType.Video; break; - case ['php', 'js', 'css'].includes(ext): + case ['php', 'js', 'css', 'xml', 'html'].includes(ext) || 'text/html' == mimeType: result = FileType.Code; break; case 'eml' == ext || ['message/delivery-status', 'message/rfc822'].includes(mimeType): result = FileType.Eml; break; - case 'text/html' == mimeType || 'html' == ext: - result = FileType.Html; - break; case 'text' == mimeTypeParts[0] || 'txt' == ext || 'log' == ext: result = FileType.Text; break; @@ -204,19 +197,17 @@ export const FileInfo = { case 'pdf' == type || 'pdf' == ext: result = FileType.Pdf; break; - case [app+'pgp-signature', app+'pgp-keys'].includes(mimeType) || - ['asc', 'pem', 'ppk'].includes(ext): + case [app+'pgp-signature', app+'pgp-keys'].includes(mimeType) + || ['asc', 'pem', 'ppk'].includes(ext) + || [app+'pkcs7-signature'].includes(mimeType) || 'p7s' == ext: result = FileType.Certificate; break; - case [app+'pkcs7-signature'].includes(mimeType) || 'p7s' == ext: - result = FileType.CertificateBin; - break; case match(msOffice+'.wordprocessingml') || match(openDoc+'.text') || match('vnd.ms-word') || ['rtf', 'msword', 'vnd.msword'].includes(type): - result = FileType.WordText; + result = FileType.Word; break; case match(msOffice+'.spreadsheetml') || match(openDoc+'.spreadsheet') || match('ms-excel'): - result = FileType.Sheet; + result = FileType.Spreadsheet; break; case match(msOffice+'.presentationml') || match(openDoc+'.presentation') || match('ms-powerpoint'): result = FileType.Presentation; @@ -232,43 +223,22 @@ export const FileInfo = { * @returns {string} */ getTypeIconClass: fileType => { - let result = ['icon-file', '']; + let result = 'icon-file'; switch (fileType) { case FileType.Text: case FileType.Eml: - case FileType.WordText: - result[0] += '-text'; - break; - case FileType.Html: - case FileType.Code: - result[0] += '-code'; - break; - case FileType.Image: - result[0] += '-image'; - break; - case FileType.Audio: - result[0] += '-music'; - break; - case FileType.Video: - result[0] += '-movie'; - break; - case FileType.Archive: - result[0] += '-zip'; - break; - case FileType.Certificate: - case FileType.CertificateBin: - result[0] += '-certificate'; - break; - case FileType.Sheet: - result[0] += '-excel'; - break; - case FileType.Presentation: - result[0] += '-chart-graph'; - break; case FileType.Pdf: - result[1] = 'pdf'; - break; - // no default + case FileType.Word: + return result + '-text'; + case FileType.Code: + case FileType.Image: + case FileType.Audio: + case FileType.Video: + case FileType.Archive: + case FileType.Certificate: + case FileType.Spreadsheet: + case FileType.Presentation: + return result + '-' + fileType; } return result; }, @@ -282,7 +252,7 @@ export const FileInfo = { getCombinedIconClass: data => { if (isNonEmptyArray(data)) { let icons = data - .map(item => item ? FileInfo.getIconClass(FileInfo.getExtension(item[0]), item[1])[0] : '') + .map(item => item ? FileInfo.getIconClass(FileInfo.getExtension(item[0]), item[1]) : '') .validUnique(); return (icons && 1 === icons.length && 'icon-file' !== icons[0]) diff --git a/dev/Model/Attachment.js b/dev/Model/Attachment.js index 880090761..4c2573857 100644 --- a/dev/Model/Attachment.js +++ b/dev/Model/Attachment.js @@ -10,8 +10,6 @@ import { AbstractModel } from 'Knoin/AbstractModel'; import { SMAudio } from 'Common/Audio'; -const bAllowPdfPreview = undefined !== navigator.mimeTypes['application/pdf']; - export class AttachmentModel extends AbstractModel { constructor() { super(); @@ -92,27 +90,21 @@ export class AttachmentModel extends AbstractModel { * @returns {boolean} */ isText() { - return ( - FileType.Text === this.fileType || - FileType.Eml === this.fileType || - FileType.Certificate === this.fileType || - FileType.Html === this.fileType || - FileType.Code === this.fileType - ); + return FileType.Text === this.fileType || FileType.Eml === this.fileType; } /** * @returns {boolean} */ - isPdf() { - return FileType.Pdf === this.fileType; + pdfPreview() { + return null != navigator.mimeTypes['application/pdf'] && FileType.Pdf === this.fileType; } /** * @returns {boolean} */ hasPreview() { - return this.isImage() || (this.isPdf() && bAllowPdfPreview) || this.isText(); + return this.isImage() || this.pdfPreview() || this.isText(); } /** @@ -155,13 +147,6 @@ export class AttachmentModel extends AbstractModel { return link ? 'background:url(' + link + ')' : ''; } - /** - * @returns {string} - */ - linkPreviewAsPlain() { - return serverRequestRaw('ViewAsPlain', this.download); - } - /** * @returns {string} */ @@ -169,11 +154,11 @@ export class AttachmentModel extends AbstractModel { let result = ''; switch (true) { case this.isImage(): - case this.isPdf() && bAllowPdfPreview: + case this.pdfPreview(): result = this.linkPreview(); break; case this.isText(): - result = this.linkPreviewAsPlain(); + result = serverRequestRaw('ViewAsPlain', this.download); break; // no default } @@ -181,18 +166,6 @@ export class AttachmentModel extends AbstractModel { return result; } - /** - * @returns {string} - */ - generateTransferDownloadUrl() { - let link = this.linkDownload(); - if ('http' !== link.substr(0, 4)) { - link = location.protocol + '//' + location.host + location.pathname + link; - } - - return this.mimeType + ':' + this.fileName + ':' + link; - } - /** * @param {AttachmentModel} attachment * @param {*} event @@ -201,7 +174,11 @@ export class AttachmentModel extends AbstractModel { eventDragStart(attachment, event) { const localEvent = event.originalEvent || event; if (attachment && localEvent && localEvent.dataTransfer && localEvent.dataTransfer.setData) { - localEvent.dataTransfer.setData('DownloadURL', this.generateTransferDownloadUrl()); + let link = this.linkDownload(); + if ('http' !== link.substr(0, 4)) { + link = location.protocol + '//' + location.host + location.pathname + link; + } + localEvent.dataTransfer.setData('DownloadURL', this.mimeType + ':' + this.fileName + ':' + link); } return true; @@ -211,13 +188,6 @@ export class AttachmentModel extends AbstractModel { * @returns {string} */ iconClass() { - return FileInfo.getTypeIconClass(this.fileType)[0]; - } - - /** - * @returns {string} - */ - iconText() { - return FileInfo.getTypeIconClass(this.fileType)[1]; + return FileInfo.getTypeIconClass(this.fileType); } } diff --git a/dev/Model/ComposeAttachment.js b/dev/Model/ComposeAttachment.js index 6ee36dade..231e1deb4 100644 --- a/dev/Model/ComposeAttachment.js +++ b/dev/Model/ComposeAttachment.js @@ -96,13 +96,6 @@ export class ComposeAttachmentModel extends AbstractModel { * @returns {string} */ iconClass() { - return FileInfo.getIconClass(this.fileExt(), this.mimeType())[0]; - } - - /** - * @returns {string} - */ - iconText() { - return FileInfo.getIconClass(this.fileExt(), this.mimeType())[1]; + return FileInfo.getIconClass(this.fileExt(), this.mimeType()); } } diff --git a/dev/Styles/Attachmnets.less b/dev/Styles/Attachmnets.less index de7271c2a..85ddab889 100644 --- a/dev/Styles/Attachmnets.less +++ b/dev/Styles/Attachmnets.less @@ -103,16 +103,6 @@ } } - .attachmentIconText { - display: inline-block; - font-size: 28px; - width: 60px; - height: 56px; - color: #aaa; - line-height: 56px; - font-style: normal; - } - /* .attachmentIconParent.hasPreview.isImage { .iconMain { diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Base/Utils.php b/snappymail/v/0.0.0/app/libraries/MailSo/Base/Utils.php index abbb19d7b..053a12f6b 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Base/Utils.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Base/Utils.php @@ -744,7 +744,6 @@ END; 'sql' => 'text/plain', 'cfg' => 'text/plain', 'conf' => 'text/plain', - 'asc' => 'text/plain', 'rtx' => 'text/richtext', 'vcard' => 'text/vcard', 'vcf' => 'text/vcard', @@ -759,6 +758,7 @@ END; 'hlp' => 'application/winhlp', 'wgt' => 'application/widget', 'chm' => 'application/vnd.ms-htmlhelp', + 'asc' => 'application/pgp-signature', 'p10' => 'application/pkcs10', 'p7c' => 'application/pkcs7-mime', 'p7m' => 'application/pkcs7-mime', @@ -770,11 +770,7 @@ END; 'pl' => 'text/perl', 'css' => 'text/css', 'asp' => 'text/asp', - 'php' => 'application/x-httpd-php', - 'php3' => 'application/x-httpd-php', - 'php4' => 'application/x-httpd-php', - 'php5' => 'application/x-httpd-php', - 'phtml' => 'application/x-httpd-php', + 'php' => 'application/x-php', // images 'png' => 'image/png', diff --git a/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html b/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html index ecb1fafa4..30ad24e94 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html @@ -278,15 +278,13 @@
- - +
- +
ā–¶
@@ -316,8 +314,8 @@ āœ– - + diff --git a/snappymail/v/0.0.0/app/templates/Views/User/PopupsCompose.html b/snappymail/v/0.0.0/app/templates/Views/User/PopupsCompose.html index 6ee35cd1d..f87d884b5 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/PopupsCompose.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/PopupsCompose.html @@ -171,8 +171,7 @@
  • - - +
    diff --git a/vendors/fontastic/styles.css b/vendors/fontastic/styles.css index b2cd0bfea..936093faa 100644 --- a/vendors/fontastic/styles.css +++ b/vendors/fontastic/styles.css @@ -55,13 +55,13 @@ .icon-file-code::before { content: "\e030"; } -.icon-file-chart-graph::before { +.icon-file-presentation::before { content: "\e031"; /* šŸ“Š */ } -.icon-file-zip::before { +.icon-file-archive::before { content: "\e032"; } -.icon-file-music::before { +.icon-file-audio::before { content: "\e033"; } .icon-file-text::before { @@ -76,10 +76,10 @@ .icon-attachment::before { content: "šŸ“Ž"; } -.icon-file-excel::before { +.icon-file-spreadsheet::before { content: "\e03a"; } -.icon-file-movie::before { +.icon-file-video::before { content: "\e03b"; } .icon-folder-add::before { From 910e7fbfd5351b66860ed3edab16a96687b7bf31 Mon Sep 17 00:00:00 2001 From: djmaze Date: Tue, 13 Apr 2021 17:25:36 +0200 Subject: [PATCH 2/5] Improved #79 --- .../Providers/AddressBook/PdoAddressBook.php | 28 ++--- .../app/libraries/snappymail/dav/client.php | 110 ++++++++---------- 2 files changed, 58 insertions(+), 80 deletions(-) diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php index 65f8a267e..8f972abdb 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php @@ -139,7 +139,7 @@ class PdoAddressBook { $aMatch = array(); if (\preg_match('/\/([^\/?]+)$/', $sKey, $aMatch) && !empty($aMatch[1]) && - (!$aItem['{dav:}resourcetype'] || !$aItem['{dav:}resourcetype']->is('{DAV:}collection'))) + (!$aItem['{dav:}resourcetype'] || !\in_array('{DAV:}collection', $aItem['{dav:}resourcetype']))) { $sVcfFileName = \urldecode(\urldecode($aMatch[1])); $sKeyID = \preg_replace('/\.vcf$/i', '', $sVcfFileName); @@ -280,9 +280,7 @@ class PdoAddressBook if (empty($sNextPath)) { - $oResourceType = isset($aItem['{DAV:}resourcetype']) ? $aItem['{DAV:}resourcetype'] : null; - /* @var $oResourceType \Sabre\DAV\Property\ResourceType */ - if ($oResourceType && $oResourceType->is('{DAV:}collection')) + if (isset($aItem['{DAV:}resourcetype']) && \in_array('{DAV:}collection', $aItem['{DAV:}resourcetype'])) { $sNextPath = $sKey; continue; @@ -403,15 +401,10 @@ class PdoAddressBook { if (!empty($sKey) && $aItem && isset($aItem['{DAV:}resourcetype'])) { - $oResourceType = $aItem['{DAV:}resourcetype']; - /* @var $oResourceType \Sabre\DAV\Property\ResourceType */ - - if ($oResourceType && $oResourceType->is('{DAV:}collection')) + if (\in_array('{DAV:}collection', $aItem['{DAV:}resourcetype']) + && \in_array('{urn:ietf:params:xml:ns:carddav}addressbook', $aItem['{DAV:}resourcetype'])) { - if ($oResourceType->is('{urn:ietf:params:xml:ns:carddav}addressbook')) - { - $aContactsPaths[$sKey] = isset($aItem['{DAV:}displayname']) ? \trim($aItem['{DAV:}displayname']) : ''; - } + $aContactsPaths[$sKey] = isset($aItem['{DAV:}displayname']) ? \trim($aItem['{DAV:}displayname']) : ''; } } } @@ -451,11 +444,8 @@ class PdoAddressBook { if (!empty($sKey) && isset($aItem['{DAV:}resourcetype'])) { - $oResourceType = $aItem['{DAV:}resourcetype']; - /* @var $oResourceType \Sabre\DAV\Property\ResourceType */ - - if ($oResourceType && $oResourceType->is('{DAV:}collection') && - $oResourceType->is('{urn:ietf:params:xml:ns:carddav}addressbook')) + if (\in_array('{DAV:}collection', $aItem['{DAV:}resourcetype']) + && \in_array('{urn:ietf:params:xml:ns:carddav}addressbook', $aItem['{DAV:}resourcetype'])) { $bGood = true; } @@ -471,7 +461,7 @@ class PdoAddressBook return $bGood; } - public function getDavClientFromUrl(string $sUrl, string $sUser, string $sPassword, string $sProxy = '') : DAVClient + private function getDavClientFromUrl(string $sUrl, string $sUser, string $sPassword, string $sProxy = '') : DAVClient { if (!\preg_match('/^http[s]?:\/\//i', $sUrl)) { @@ -515,7 +505,7 @@ class PdoAddressBook return $oClient; } - public function getDavClient(string $sUrl, string $sUser, string $sPassword, string $sProxy = '') : ?DAVClient + private function getDavClient(string $sUrl, string $sUser, string $sPassword, string $sProxy = '') : ?DAVClient { $aMatch = array(); $sUserAddressBookNameName = ''; diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/dav/client.php b/snappymail/v/0.0.0/app/libraries/snappymail/dav/client.php index 2bcff53d5..33428533e 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/dav/client.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/dav/client.php @@ -10,21 +10,7 @@ namespace SnappyMail\DAV; class Client { - /** - * The propertyMap is a key-value array. - * - * If you use the propertyMap, any {DAV:}multistatus responses with the - * proeprties listed in this array, will automatically be mapped to a - * respective class. - * - * The {DAV:}resourcetype property is automatically added. This maps to - * Sabre\DAV\Property\ResourceType - * - * @var array - */ - public $propertyMap = array( -// '{DAV:}resourcetype' => 'SnappyMail\\DAV\\Property\\ResourceType' - ); +// public $__UrlPath__; protected $baseUri; @@ -62,6 +48,40 @@ class Client $this->HTTP->verify_peer = $value; } + /** + * Performs an actual HTTP request, and returns the result. + * + * If the specified url is relative, it will be expanded based on the base url. + */ + public function request(string $method, string $url = '', string $body = null, array $headers = array()) : \SnappyMail\HTTP\Response + { + if (!\preg_match('@^(https?:)?//@', $url)) { + // If the url starts with a slash, we must calculate the url based off + // the root of the base url. + if (0 === \strpos($url, '/')) { + $parts = \parse_url($this->baseUri); + $url = $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port'])?':' . $parts['port']:'') . $url; + } else { + $url = $this->baseUri . $url; + } + } + $response = $this->HTTP->doRequest('PROPFIND', $url, $body, $headers); + if (301 == $response->status) { + // Like: RewriteRule ^\.well-known/carddav /nextcloud/remote.php/dav [R=301,L] + $location = $response->getRedirectLocation(); + \trigger_error("Redirect {$url} to {$location}"); + $url = \preg_replace('@^(https?:)?//[^/]+[/$]@', '/', $location); + $parts = \parse_url($this->baseUri); + $url = $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port'])?':' . $parts['port']:'') . $url; + $response = $this->HTTP->doRequest('PROPFIND', $url, $body, $headers); + } + if (300 <= $response->status) { + throw new \SnappyMail\HTTP\Exception("PROPFIND {$url}", $response->status, $response); + } + + return $response; + } + /** * Does a PROPFIND request * @@ -98,35 +118,10 @@ class Client $body .= ' ' . "\n"; $body .= ''; - if (!\preg_match('@^(https?:)?//@', $url)) { - // If the url starts with a slash, we must calculate the url based off - // the root of the base url. - if (0 === \strpos($url, '/')) { - $parts = \parse_url($this->baseUri); - $url = $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port'])?':' . $parts['port']:'') . $url; - } else { - $url = $this->baseUri . $url; - } - } - $response = $this->HTTP->doRequest('PROPFIND', $url, $body, array( + $response = $this->request('PROPFIND', $url, $body, array( "Depth: {$depth}", 'Content-Type: application/xml' )); - if (301 == $response->status) { - // Like: RewriteRule ^\.well-known/carddav /nextcloud/remote.php/dav [R=301,L] - $location = $response->getRedirectLocation(); - \trigger_error("Redirect {$url} to {$location}"); - $url = \preg_replace('@^(https?:)?//[^/]+[/$]@', '/', $location); - $parts = \parse_url($this->baseUri); - $url = $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port'])?':' . $parts['port']:'') . $url; - $response = $this->HTTP->doRequest('PROPFIND', $url, $body, array( - "Depth: {$depth}", - 'Content-Type: application/xml' - )); - } - if (300 <= $response->status) { - throw new \SnappyMail\HTTP\Exception("PROPFIND {$url}", $response->status, $response); - } /** * Parse the WebDAV multistatus response body @@ -148,26 +143,21 @@ class Client throw new \InvalidArgumentException('The passed data is not valid XML'); } - $responseXML->registerXPathNamespace('d', 'urn:DAV'); + $ns = \array_search('urn:DAV', $responseXML->getNamespaces(true)); +// $ns_card = \array_search('urn:ietf:params:xml:ns:carddav', $responseXML->getNamespaces(true)); $result = array(); - foreach ($responseXML->xpath('d:response') as $response) { - $response->registerXPathNamespace('d', 'urn:DAV'); - $href = $response->xpath('d:href'); + foreach ($responseXML->xpath("{$ns}:response") as $response) { + $href = $response->xpath("{$ns}:href"); $href = (string) $href[0]; $properties = array(); - - foreach ($response->xpath('d:propstat') as $propStat) { - $propStat->registerXPathNamespace('d', 'urn:DAV'); - $status = $propStat->xpath('d:status'); + foreach ($response->xpath("{$ns}:propstat") as $propStat) { + $status = $propStat->xpath("{$ns}:status"); list($httpVersion, $statusCode, $message) = \explode(' ', (string)$status[0], 3); - // Only using the propertymap for results with status 200. - $propertyMap = $statusCode === '200' ? $this->propertyMap : array(); - - $properties[$statusCode] = static::parseProperties(\dom_import_simplexml($propStat), $propertyMap); + $properties[$statusCode] = static::parseProperties(\dom_import_simplexml($propStat)); } $result[$href] = $properties; @@ -218,14 +208,10 @@ class Client * If no value was given (self-closing element) null will be used as the * value. This is used in for example PROPFIND requests. * - * Complex values are supported through the propertyMap argument. The - * propertyMap should have the clark-notation properties as it's keys, and - * classnames as values. - * * When any of these properties are found, the fromDOMElement() method will be * (statically) called. The result of this method is used as the value. */ - protected static function parseProperties(\DOMElement $parentNode, array $propertyMap = array()) : array + protected static function parseProperties(\DOMElement $parentNode) : array { $propList = array(); foreach ($parentNode->childNodes as $propNode) { @@ -234,8 +220,11 @@ class Client /* If there are no elements in here, we actually get 1 text node, this special case is dedicated to netdrive */ if (XML_ELEMENT_NODE == $propNodeData->nodeType) { $propertyName = self::toClarkNotation($propNodeData); - if (isset($propertyMap[$propertyName])) { - $propList[$propertyName] = \call_user_func(array($propertyMap[$propertyName], 'fromDOMElement'), $propNodeData); + if ('{DAV:}resourcetype' === $propertyName) { + $propList[$propertyName] = []; + foreach ($propNodeData->childNodes as $resourcetype) { + $propList[$propertyName][] = self::toClarkNotation($resourcetype); + } } else { $propList[$propertyName] = $propNodeData->textContent; } @@ -245,5 +234,4 @@ class Client } return $propList; } - } From 50554061125b4713231a1ab309701eb10e6a5a3a Mon Sep 17 00:00:00 2001 From: djmaze Date: Tue, 13 Apr 2021 23:15:13 +0200 Subject: [PATCH 3/5] Resolved more CardDAV issues for #79 --- .../Providers/AddressBook/CardDAV.php | 484 ++++++++++++++++++ .../Providers/AddressBook/PdoAddressBook.php | 479 +---------------- .../app/libraries/snappymail/dav/client.php | 9 +- 3 files changed, 492 insertions(+), 480 deletions(-) create mode 100644 snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/CardDAV.php diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/CardDAV.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/CardDAV.php new file mode 100644 index 000000000..ae1422a0a --- /dev/null +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/CardDAV.php @@ -0,0 +1,484 @@ +oLogger->Write('PROPFIND '.$sPath, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); + + $aResponse = $oClient->propFind($sPath, array( + '{DAV:}getlastmodified', + '{DAV:}resourcetype', + '{DAV:}getetag' + ), 1); + +// $this->oLogger->WriteDump($aResponse); + } + catch (\Throwable $oException) + { + $this->oLogger->WriteException($oException); + } + + if (\is_array($aResponse)) + { + $mResult = array(); + foreach ($aResponse as $sKey => $aItem) + { + $sKey = \rtrim(\trim($sKey), '\\/'); + if (!empty($sKey) && is_array($aItem)) + { + $aItem = \array_change_key_case($aItem, \CASE_LOWER); + if (isset($aItem['{DAV:}getetag'])) + { + $aMatch = array(); + if (\preg_match('/\/([^\/?]+)$/', $sKey, $aMatch) && !empty($aMatch[1]) + && !static::hasDAVCollection($aItem)) + { + $sVcfFileName = \urldecode(\urldecode($aMatch[1])); + $sKeyID = \preg_replace('/\.vcf$/i', '', $sVcfFileName); + + $mResult[$sKeyID] = array( + 'deleted' => false, + 'uid' => $sKeyID, + 'vcf' => $sVcfFileName, + 'etag' => \trim(\trim($aItem['{DAV:}getetag']), '"\''), + 'lastmodified' => '', + 'changed' => 0 + ); + + if (isset($aItem['{DAV:}getlastmodified'])) + { + $mResult[$sKeyID]['lastmodified'] = $aItem['{DAV:}getlastmodified']; + $mResult[$sKeyID]['changed'] = \MailSo\Base\DateTimeHelper::ParseRFC2822DateString( + $aItem['{DAV:}getlastmodified']); + } + else + { + $mResult[$sKeyID]['changed'] = \MailSo\Base\DateTimeHelper::TryToParseSpecEtagFormat($mResult[$sKeyID]['etag']); + $mResult[$sKeyID]['lastmodified'] = 0 < $mResult[$sKeyID]['changed'] ? + \gmdate('c', $mResult[$sKeyID]['changed']) : ''; + } + + $mResult[$sKeyID]['changed_'] = \gmdate('c', $mResult[$sKeyID]['changed']); + } + } + } + } + } + + return $mResult; + } + + protected function davClientRequest(DAVClient $oClient, string $sCmd, string $sUrl, $mData = null) : ?array + { + \MailSo\Base\Utils::ResetTimeLimit(); + + $this->oLogger->Write($sCmd.' '.$sUrl.(('PUT' === $sCmd || 'POST' === $sCmd) && null !== $mData ? ' ('.\strlen($mData).')' : ''), + \MailSo\Log\Enumerations\Type::INFO, 'DAV'); + +// if ('PUT' === $sCmd || 'POST' === $sCmd) +// { +// $this->oLogger->Write($mData, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); +// } + + $aResponse = null; + try + { + if (('PUT' === $sCmd || 'POST' === $sCmd) && null !== $mData) + { + $aResponse = $oClient->request($sCmd, $sUrl, $mData, array( + 'Content-Type' => 'text/vcard; charset=utf-8' + )); + } + else + { + $aResponse = $oClient->request($sCmd, $sUrl); + } + +// if ('GET' === $sCmd) +// { +// $this->oLogger->WriteDump($aResponse, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); +// } + } + catch (\Throwable $oException) + { + $this->oLogger->WriteException($oException); + } + + return $aResponse; + } + + private function detectionPropFind(DAVClient $oClient, string $sPath) : ?array + { + $aResponse = null; + + try + { + $this->oLogger->Write('PROPFIND '.$sPath, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); + + $aResponse = $oClient->propFind($sPath, array( + '{DAV:}current-user-principal', + '{DAV:}resourcetype', + '{DAV:}displayname', + '{urn:ietf:params:xml:ns:carddav}addressbook-home-set' + ), 1); + +// $this->oLogger->WriteDump($aResponse); + } + catch (\Throwable $oException) + { + $this->oLogger->WriteException($oException); + } + + return $aResponse; + } + + private function getContactsPaths(DAVClient $oClient, string $sUser, string $sPassword, string $sProxy = '') : array + { + $aContactsPaths = array(); + + $sCurrentUserPrincipal = ''; + $sAddressbookHomeSet = ''; + +// [{DAV:}current-user-principal] => /cloud/remote.php/carddav/principals/admin/ +// [{urn:ietf:params:xml:ns:carddav}addressbook-home-set] => /cloud/remote.php/carddav/addressbooks/admin/ + + $aResponse = $this->detectionPropFind($oClient, '/.well-known/carddav'); + + $sNextPath = ''; + $sFirstNextPath = ''; + if (\is_array($aResponse)) + { + foreach ($aResponse as $sKey => $aItem) + { + if (empty($sAddressbookHomeSet) && !empty($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']) && + false === \strpos($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set'], '/calendar-proxy')) + { + $sAddressbookHomeSet = $aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']; + continue; + } + + if (empty($sCurrentUserPrincipal) && !empty($aItem['{DAV:}current-user-principal'])) + { + $sCurrentUserPrincipal = $aItem['{DAV:}current-user-principal']; + continue; + } + + if (!empty($sKey)) + { + if (empty($sFirstNextPath)) + { + $sFirstNextPath = $sKey; + } + + if (empty($sNextPath)) + { + if (static::hasDAVCollection($aItem)) + { + $sNextPath = $sKey; + continue; + } + } + } + } + + if (empty($sNextPath) && empty($sCurrentUserPrincipal) && empty($sAddressbookHomeSet) && !empty($sFirstNextPath)) + { + $sNextPath = $sFirstNextPath; + } + } + + if (empty($sCurrentUserPrincipal) && empty($sAddressbookHomeSet)) + { + if (empty($sNextPath)) + { + return $aContactsPaths; + } + else + { + if (\preg_match('/^http[s]?:\/\//i', $sNextPath)) + { + $oClient = $this->getDavClientFromUrl($sNextPath, $sUser, $sPassword, $sProxy); + if ($oClient) + { + $sNextPath = $oClient->__UrlPath__; + } + else + { + return $aContactsPaths; + } + } + + $aResponse = $this->detectionPropFind($oClient, $sNextPath); + if (\is_array($aResponse)) + { + foreach ($aResponse as $sKey => $aItem) + { + if (empty($sAddressbookHomeSet) && !empty($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']) && + false === \strpos($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set'], '/calendar-proxy')) + { + $sAddressbookHomeSet = $aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']; + continue; + } + + if (empty($sCurrentUserPrincipal) && !empty($aItem['{DAV:}current-user-principal'])) + { + $sCurrentUserPrincipal = $aItem['{DAV:}current-user-principal']; + continue; + } + } + } + } + } + + if (empty($sAddressbookHomeSet)) + { + if (empty($sCurrentUserPrincipal)) + { + return $aContactsPaths; + } + else + { + if (\preg_match('/^http[s]?:\/\//i', $sCurrentUserPrincipal)) + { + $oClient = $this->getDavClientFromUrl($sCurrentUserPrincipal, $sUser, $sPassword, $sProxy); + if ($oClient) + { + $sCurrentUserPrincipal = $oClient->__UrlPath__; + } + else + { + return $aContactsPaths; + } + } + + $aResponse = $this->detectionPropFind($oClient, $sCurrentUserPrincipal); + if (\is_array($aResponse)) + { + foreach ($aResponse as $sKey => $aItem) + { + if (empty($sAddressbookHomeSet) && !empty($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']) && + false === \strpos($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set'], '/calendar-proxy')) + { + $sAddressbookHomeSet = $aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']; + continue; + } + } + } + } + } + + if (empty($sAddressbookHomeSet)) + { + return $aContactsPaths; + } + else + { + if (\preg_match('/^http[s]?:\/\//i', $sAddressbookHomeSet)) + { + $oClient = $this->getDavClientFromUrl($sAddressbookHomeSet, $sUser, $sPassword, $sProxy); + if ($oClient) + { + $sAddressbookHomeSet = $oClient->__UrlPath__; + } + else + { + return $aContactsPaths; + } + } + + $aResponse = $this->detectionPropFind($oClient, $sAddressbookHomeSet); + if (\is_array($aResponse)) + { + foreach ($aResponse as $sKey => $aItem) + { + if (!empty($sKey) && static::hasDAVCollection($aItem) + && \in_array('{urn:ietf:params:xml:ns:carddav}addressbook', $aItem['{DAV:}resourcetype'])) + { + $aContactsPaths[$sKey] = isset($aItem['{DAV:}displayname']) ? \trim($aItem['{DAV:}displayname']) : ''; + } + } + } + } + + return $aContactsPaths; + } + + private function checkContactsPath(DAVClient $oClient, string $sPath) : bool + { + if (!$oClient) + { + return false; + } + + $this->oLogger->Write('PROPFIND '.$sPath, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); + + $aResponse = null; + try + { + $aResponse = $oClient->propFind($sPath, array( + '{DAV:}resourcetype' + ), 1); + +// $this->oLogger->WriteDump($aResponse); + } + catch (\Throwable $oException) + { + $this->oLogger->WriteException($oException); + } + + $bGood = false; + if (\is_array($aResponse)) + { + foreach ($aResponse as $sKey => $aItem) + { + if (!empty($sKey) && static::hasDAVCollection($aItem) + && \in_array('{urn:ietf:params:xml:ns:carddav}addressbook', $aItem['{DAV:}resourcetype'])) + { + $bGood = true; + } + } + } + + if ($bGood) + { + $oClient->__UrlPath__ = $sPath; + } + + return $bGood; + } + + private function getDavClientFromUrl(string $sUrl, string $sUser, string $sPassword, string $sProxy = '') : DAVClient + { + if (!\preg_match('/^http[s]?:\/\//i', $sUrl)) + { + $sUrl = \preg_replace('/^fruux\.com/i', 'dav.fruux.com', $sUrl); + $sUrl = \preg_replace('/^icloud\.com/i', 'contacts.icloud.com', $sUrl); + $sUrl = \preg_replace('/^gmail\.com/i', 'google.com', $sUrl); + $sUrl = 'https://'.$sUrl; + } + + $aUrl = \parse_url($sUrl); + if (!\is_array($aUrl)) + { + $aUrl = array(); + } + + $aUrl['scheme'] = $aUrl['scheme'] ?? 'http'; + $aUrl['host'] = $aUrl['host'] ?? 'localhost'; + $aUrl['port'] = $aUrl['port'] ?? 0; + $aUrl['path'] = isset($aUrl['path']) ? \rtrim($aUrl['path'], '\\/').'/' : '/'; + + $aSettings = array( + 'baseUri' => $aUrl['scheme'].'://'.$aUrl['host'].($aUrl['port'] ? ':'.$aUrl['port'] : ''), + 'userName' => $sUser, + 'password' => $sPassword + ); + + $this->oLogger->AddSecret($sPassword); + + if (!empty($sProxy)) + { + $aSettings['proxy'] = $sProxy; + } + + $oClient = new DAVClient($aSettings); + $oClient->setVerifyPeer(false); + + $oClient->__UrlPath__ = $aUrl['path']; + + $this->oLogger->Write('DavClient: User: '.$aSettings['userName'].', Url: '.$sUrl, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); + + return $oClient; + } + + protected function getDavClient(string $sUrl, string $sUser, string $sPassword, string $sProxy = '') : ?DAVClient + { + $aMatch = array(); + $sUserAddressBookNameName = ''; + + if (\preg_match('/\|(.+)$/', $sUrl, $aMatch) && !empty($aMatch[1])) + { + $sUserAddressBookNameName = \trim($aMatch[1]); + $sUserAddressBookNameName = \MailSo\Base\Utils::StrToLowerIfAscii($sUserAddressBookNameName); + + $sUrl = \preg_replace('/\|(.+)$/', '', $sUrl); + } + + $oClient = $this->getDavClientFromUrl($sUrl, $sUser, $sPassword, $sProxy); + + $sPath = $oClient->__UrlPath__; + + $bGood = true; + if ('' === $sPath || '/' === $sPath || !$this->checkContactsPath($oClient, $sPath)) + { + $sNewPath = ''; + + $aPaths = $this->getContactsPaths($oClient, $sUser, $sPassword, $sProxy); + $this->oLogger->WriteDump($aPaths); + + if (\is_array($aPaths)) + { + if (1 < \count($aPaths)) + { + if ('' !== $sUserAddressBookNameName) + { + foreach ($aPaths as $sKey => $sValue) + { + $sValue = \MailSo\Base\Utils::StrToLowerIfAscii(\trim($sValue)); + if ($sValue === $sUserAddressBookNameName) + { + $sNewPath = $sKey; + break; + } + } + } + + if (empty($sNewPath)) + { + foreach ($aPaths as $sKey => $sValue) + { + $sValue = \MailSo\Base\Utils::StrToLowerIfAscii($sValue); + if (\in_array($sValue, array('contacts', 'default', 'addressbook', 'address book'))) + { + $sNewPath = $sKey; + break; + } + } + } + } + + if (empty($sNewPath)) + { + foreach ($aPaths as $sKey => $sValue) + { + $sNewPath = $sKey; + break; + } + } + } + + $sPath = $sNewPath; + + $bGood = $this->checkContactsPath($oClient, $sPath); + } + + return $bGood ? $oClient : null; + } + + private static function hasDAVCollection($aItem) + { + return !empty($aItem['{DAV:}resourcetype']) + && \is_array($aItem['{DAV:}resourcetype']) + && \in_array('{DAV:}collection', $aItem['{DAV:}resourcetype']); + } + +} diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php index 8f972abdb..328401543 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php @@ -3,12 +3,13 @@ namespace RainLoop\Providers\AddressBook; use \RainLoop\Providers\AddressBook\Enumerations\PropertyType; -use \SnappyMail\DAV\Client as DAVClient; class PdoAddressBook extends \RainLoop\Common\PdoAbstract implements \RainLoop\Providers\AddressBook\AddressBookInterface { + use CardDAV; + /** * @var string */ @@ -105,480 +106,6 @@ class PdoAddressBook return $aResult; } - private function prepearRemoteSyncData(DAVClient $oClient, string $sPath) - { - $mResult = false; - $aResponse = null; - try - { - $this->oLogger->Write('PROPFIND '.$sPath, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); - - $aResponse = $oClient->propFind($sPath, array( - '{DAV:}getlastmodified', - '{DAV:}resourcetype', - '{DAV:}getetag' - ), 1); - -// $this->oLogger->WriteDump($aResponse); - } - catch (\Throwable $oException) - { - $this->oLogger->WriteException($oException); - } - - if (\is_array($aResponse)) - { - $mResult = array(); - foreach ($aResponse as $sKey => $aItem) - { - $sKey = \rtrim(\trim($sKey), '\\/'); - if (!empty($sKey) && is_array($aItem)) - { - $aItem = \array_change_key_case($aItem, \CASE_LOWER); - if (isset($aItem['{dav:}getetag'])) - { - $aMatch = array(); - if (\preg_match('/\/([^\/?]+)$/', $sKey, $aMatch) && !empty($aMatch[1]) && - (!$aItem['{dav:}resourcetype'] || !\in_array('{DAV:}collection', $aItem['{dav:}resourcetype']))) - { - $sVcfFileName = \urldecode(\urldecode($aMatch[1])); - $sKeyID = \preg_replace('/\.vcf$/i', '', $sVcfFileName); - - $mResult[$sKeyID] = array( - 'deleted' => false, - 'uid' => $sKeyID, - 'vcf' => $sVcfFileName, - 'etag' => \trim(\trim($aItem['{dav:}getetag']), '"\''), - 'lastmodified' => '', - 'changed' => 0 - ); - - if (isset($aItem['{dav:}getlastmodified'])) - { - $mResult[$sKeyID]['lastmodified'] = $aItem['{dav:}getlastmodified']; - $mResult[$sKeyID]['changed'] = \MailSo\Base\DateTimeHelper::ParseRFC2822DateString( - $aItem['{dav:}getlastmodified']); - } - else - { - $mResult[$sKeyID]['changed'] = \MailSo\Base\DateTimeHelper::TryToParseSpecEtagFormat($mResult[$sKeyID]['etag']); - $mResult[$sKeyID]['lastmodified'] = 0 < $mResult[$sKeyID]['changed'] ? - \gmdate('c', $mResult[$sKeyID]['changed']) : ''; - } - - $mResult[$sKeyID]['changed_'] = \gmdate('c', $mResult[$sKeyID]['changed']); - } - } - } - } - } - - return $mResult; - } - - private function davClientRequest(DAVClient $oClient, string $sCmd, string $sUrl, $mData = null) : ?array - { - \MailSo\Base\Utils::ResetTimeLimit(); - - $this->oLogger->Write($sCmd.' '.$sUrl.(('PUT' === $sCmd || 'POST' === $sCmd) && null !== $mData ? ' ('.\strlen($mData).')' : ''), - \MailSo\Log\Enumerations\Type::INFO, 'DAV'); - -// if ('PUT' === $sCmd || 'POST' === $sCmd) -// { -// $this->oLogger->Write($mData, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); -// } - - $aResponse = null; - try - { - if (('PUT' === $sCmd || 'POST' === $sCmd) && null !== $mData) - { - $aResponse = $oClient->request($sCmd, $sUrl, $mData, array( - 'Content-Type' => 'text/vcard; charset=utf-8' - )); - } - else - { - $aResponse = $oClient->request($sCmd, $sUrl); - } - -// if ('GET' === $sCmd) -// { -// $this->oLogger->WriteDump($aResponse, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); -// } - } - catch (\Throwable $oException) - { - $this->oLogger->WriteException($oException); - } - - return $aResponse; - } - - private function detectionPropFind(DAVClient $oClient, string $sPath) : ?array - { - $aResponse = null; - - try - { - $this->oLogger->Write('PROPFIND '.$sPath, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); - - $aResponse = $oClient->propFind($sPath, array( - '{DAV:}current-user-principal', - '{DAV:}resourcetype', - '{DAV:}displayname', - '{urn:ietf:params:xml:ns:carddav}addressbook-home-set' - ), 1); - -// $this->oLogger->WriteDump($aResponse); - } - catch (\Throwable $oException) - { - $this->oLogger->WriteException($oException); - } - - return $aResponse; - } - - private function getContactsPaths(DAVClient $oClient, string $sUser, string $sPassword, string $sProxy = '') : array - { - $aContactsPaths = array(); - - $sCurrentUserPrincipal = ''; - $sAddressbookHomeSet = ''; - -// [{DAV:}current-user-principal] => /cloud/remote.php/carddav/principals/admin/ -// [{urn:ietf:params:xml:ns:carddav}addressbook-home-set] => /cloud/remote.php/carddav/addressbooks/admin/ - - $aResponse = $this->detectionPropFind($oClient, '/.well-known/carddav'); - - $sNextPath = ''; - $sFirstNextPath = ''; - if (\is_array($aResponse)) - { - foreach ($aResponse as $sKey => $aItem) - { - if (empty($sAddressbookHomeSet) && !empty($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']) && - false === \strpos($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set'], '/calendar-proxy')) - { - $sAddressbookHomeSet = $aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']; - continue; - } - - if (empty($sCurrentUserPrincipal) && !empty($aItem['{DAV:}current-user-principal'])) - { - $sCurrentUserPrincipal = $aItem['{DAV:}current-user-principal']; - continue; - } - - if (!empty($sKey)) - { - if (empty($sFirstNextPath)) - { - $sFirstNextPath = $sKey; - } - - if (empty($sNextPath)) - { - if (isset($aItem['{DAV:}resourcetype']) && \in_array('{DAV:}collection', $aItem['{DAV:}resourcetype'])) - { - $sNextPath = $sKey; - continue; - } - } - } - } - - if (empty($sNextPath) && empty($sCurrentUserPrincipal) && empty($sAddressbookHomeSet) && !empty($sFirstNextPath)) - { - $sNextPath = $sFirstNextPath; - } - } - - if (empty($sCurrentUserPrincipal) && empty($sAddressbookHomeSet)) - { - if (empty($sNextPath)) - { - return $aContactsPaths; - } - else - { - if (\preg_match('/^http[s]?:\/\//i', $sNextPath)) - { - $oClient = $this->getDavClientFromUrl($sNextPath, $sUser, $sPassword, $sProxy); - if ($oClient) - { - $sNextPath = $oClient->__UrlPath__; - } - else - { - return $aContactsPaths; - } - } - - $aResponse = $this->detectionPropFind($oClient, $sNextPath); - if (\is_array($aResponse)) - { - foreach ($aResponse as $sKey => $aItem) - { - if (empty($sAddressbookHomeSet) && !empty($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']) && - false === \strpos($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set'], '/calendar-proxy')) - { - $sAddressbookHomeSet = $aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']; - continue; - } - - if (empty($sCurrentUserPrincipal) && !empty($aItem['{DAV:}current-user-principal'])) - { - $sCurrentUserPrincipal = $aItem['{DAV:}current-user-principal']; - continue; - } - } - } - } - } - - if (empty($sAddressbookHomeSet)) - { - if (empty($sCurrentUserPrincipal)) - { - return $aContactsPaths; - } - else - { - if (\preg_match('/^http[s]?:\/\//i', $sCurrentUserPrincipal)) - { - $oClient = $this->getDavClientFromUrl($sCurrentUserPrincipal, $sUser, $sPassword, $sProxy); - if ($oClient) - { - $sCurrentUserPrincipal = $oClient->__UrlPath__; - } - else - { - return $aContactsPaths; - } - } - - $aResponse = $this->detectionPropFind($oClient, $sCurrentUserPrincipal); - if (\is_array($aResponse)) - { - foreach ($aResponse as $sKey => $aItem) - { - if (empty($sAddressbookHomeSet) && !empty($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']) && - false === \strpos($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set'], '/calendar-proxy')) - { - $sAddressbookHomeSet = $aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']; - continue; - } - } - } - } - } - - if (empty($sAddressbookHomeSet)) - { - return $aContactsPaths; - } - else - { - if (\preg_match('/^http[s]?:\/\//i', $sAddressbookHomeSet)) - { - $oClient = $this->getDavClientFromUrl($sAddressbookHomeSet, $sUser, $sPassword, $sProxy); - if ($oClient) - { - $sAddressbookHomeSet = $oClient->__UrlPath__; - } - else - { - return $aContactsPaths; - } - } - - $aResponse = $this->detectionPropFind($oClient, $sAddressbookHomeSet); - if (\is_array($aResponse)) - { - foreach ($aResponse as $sKey => $aItem) - { - if (!empty($sKey) && $aItem && isset($aItem['{DAV:}resourcetype'])) - { - if (\in_array('{DAV:}collection', $aItem['{DAV:}resourcetype']) - && \in_array('{urn:ietf:params:xml:ns:carddav}addressbook', $aItem['{DAV:}resourcetype'])) - { - $aContactsPaths[$sKey] = isset($aItem['{DAV:}displayname']) ? \trim($aItem['{DAV:}displayname']) : ''; - } - } - } - } - } - - return $aContactsPaths; - } - - private function checkContactsPath(DAVClient $oClient, string $sPath) : bool - { - if (!$oClient) - { - return false; - } - - $this->oLogger->Write('PROPFIND '.$sPath, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); - - $aResponse = null; - try - { - $aResponse = $oClient->propFind($sPath, array( - '{DAV:}resourcetype' - ), 1); - -// $this->oLogger->WriteDump($aResponse); - } - catch (\Throwable $oException) - { - $this->oLogger->WriteException($oException); - } - - $bGood = false; - if (\is_array($aResponse)) - { - foreach ($aResponse as $sKey => $aItem) - { - if (!empty($sKey) && isset($aItem['{DAV:}resourcetype'])) - { - if (\in_array('{DAV:}collection', $aItem['{DAV:}resourcetype']) - && \in_array('{urn:ietf:params:xml:ns:carddav}addressbook', $aItem['{DAV:}resourcetype'])) - { - $bGood = true; - } - } - } - } - - if ($bGood) - { - $oClient->__UrlPath__ = $sPath; - } - - return $bGood; - } - - private function getDavClientFromUrl(string $sUrl, string $sUser, string $sPassword, string $sProxy = '') : DAVClient - { - if (!\preg_match('/^http[s]?:\/\//i', $sUrl)) - { - $sUrl = \preg_replace('/^fruux\.com/i', 'dav.fruux.com', $sUrl); - $sUrl = \preg_replace('/^icloud\.com/i', 'contacts.icloud.com', $sUrl); - $sUrl = \preg_replace('/^gmail\.com/i', 'google.com', $sUrl); - $sUrl = 'https://'.$sUrl; - } - - $aUrl = \parse_url($sUrl); - if (!\is_array($aUrl)) - { - $aUrl = array(); - } - - $aUrl['scheme'] = $aUrl['scheme'] ?? 'http'; - $aUrl['host'] = $aUrl['host'] ?? 'localhost'; - $aUrl['port'] = $aUrl['port'] ?? 0; - $aUrl['path'] = isset($aUrl['path']) ? \rtrim($aUrl['path'], '\\/').'/' : '/'; - - $aSettings = array( - 'baseUri' => $aUrl['scheme'].'://'.$aUrl['host'].($aUrl['port'] ? ':'.$aUrl['port'] : ''), - 'userName' => $sUser, - 'password' => $sPassword - ); - - $this->oLogger->AddSecret($sPassword); - - if (!empty($sProxy)) - { - $aSettings['proxy'] = $sProxy; - } - - $oClient = new DAVClient($aSettings); - $oClient->setVerifyPeer(false); - - $oClient->__UrlPath__ = $aUrl['path']; - - $this->oLogger->Write('DavClient: User: '.$aSettings['userName'].', Url: '.$sUrl, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); - - return $oClient; - } - - private function getDavClient(string $sUrl, string $sUser, string $sPassword, string $sProxy = '') : ?DAVClient - { - $aMatch = array(); - $sUserAddressBookNameName = ''; - - if (\preg_match('/\|(.+)$/', $sUrl, $aMatch) && !empty($aMatch[1])) - { - $sUserAddressBookNameName = \trim($aMatch[1]); - $sUserAddressBookNameName = \MailSo\Base\Utils::StrToLowerIfAscii($sUserAddressBookNameName); - - $sUrl = \preg_replace('/\|(.+)$/', '', $sUrl); - } - - $oClient = $this->getDavClientFromUrl($sUrl, $sUser, $sPassword, $sProxy); - - $sPath = $oClient->__UrlPath__; - - $bGood = true; - if ('' === $sPath || '/' === $sPath || !$this->checkContactsPath($oClient, $sPath)) - { - $sNewPath = ''; - - $aPaths = $this->getContactsPaths($oClient, $sUser, $sPassword, $sProxy); - $this->oLogger->WriteDump($aPaths); - - if (\is_array($aPaths)) - { - if (1 < \count($aPaths)) - { - if ('' !== $sUserAddressBookNameName) - { - foreach ($aPaths as $sKey => $sValue) - { - $sValue = \MailSo\Base\Utils::StrToLowerIfAscii(\trim($sValue)); - if ($sValue === $sUserAddressBookNameName) - { - $sNewPath = $sKey; - break; - } - } - } - - if (empty($sNewPath)) - { - foreach ($aPaths as $sKey => $sValue) - { - $sValue = \MailSo\Base\Utils::StrToLowerIfAscii($sValue); - if (\in_array($sValue, array('contacts', 'default', 'addressbook', 'address book'))) - { - $sNewPath = $sKey; - break; - } - } - } - } - - if (empty($sNewPath)) - { - foreach ($aPaths as $sKey => $sValue) - { - $sNewPath = $sKey; - break; - } - } - } - - $sPath = $sNewPath; - - $bGood = $this->checkContactsPath($oClient, $sPath); - } - - return $bGood ? $oClient : null; - } - public function Sync(string $sEmail, string $sUrl, string $sUser, string $sPassword, string $sProxy = '') : bool { $this->SyncDatabase(); @@ -597,7 +124,7 @@ class PdoAddressBook $sPath = $oClient->__UrlPath__; - $aRemoteSyncData = $this->prepearRemoteSyncData($oClient, $sPath); + $aRemoteSyncData = $this->prepareDavSyncData($oClient, $sPath); if (false === $aRemoteSyncData) { return false; diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/dav/client.php b/snappymail/v/0.0.0/app/libraries/snappymail/dav/client.php index 33428533e..0ee873f4f 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/dav/client.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/dav/client.php @@ -144,17 +144,18 @@ class Client } $ns = \array_search('urn:DAV', $responseXML->getNamespaces(true)); + $ns = $ns ? "{$ns}:" : ''; // $ns_card = \array_search('urn:ietf:params:xml:ns:carddav', $responseXML->getNamespaces(true)); $result = array(); - foreach ($responseXML->xpath("{$ns}:response") as $response) { - $href = $response->xpath("{$ns}:href"); + foreach ($responseXML->xpath("{$ns}response") as $response) { + $href = $response->xpath("{$ns}href"); $href = (string) $href[0]; $properties = array(); - foreach ($response->xpath("{$ns}:propstat") as $propStat) { - $status = $propStat->xpath("{$ns}:status"); + foreach ($response->xpath("{$ns}propstat") as $propStat) { + $status = $propStat->xpath("{$ns}status"); list($httpVersion, $statusCode, $message) = \explode(' ', (string)$status[0], 3); $properties[$statusCode] = static::parseProperties(\dom_import_simplexml($propStat)); From 3c8d01a882e7c73ded88b8fa39d94e420bc78490 Mon Sep 17 00:00:00 2001 From: djmaze Date: Wed, 14 Apr 2021 09:24:23 +0200 Subject: [PATCH 4/5] There's no CSS .i18n-animation It was only used by jQuery.letterfx in RainLoop --- dev/Component/AbstractCheckbox.js | 1 - .../app/templates/Views/Admin/AdminLogin.html | 15 ++++++++------- .../app/templates/Views/Components/Checkbox.html | 2 +- .../Views/Components/CheckboxMaterialDesign.html | 2 +- .../v/0.0.0/app/templates/Views/User/Login.html | 15 +++++---------- .../app/templates/Views/User/SettingsGeneral.html | 8 ++------ .../app/templates/Views/User/SettingsPane.html | 2 +- 7 files changed, 18 insertions(+), 27 deletions(-) diff --git a/dev/Component/AbstractCheckbox.js b/dev/Component/AbstractCheckbox.js index 146d73eaa..e84824299 100644 --- a/dev/Component/AbstractCheckbox.js +++ b/dev/Component/AbstractCheckbox.js @@ -27,7 +27,6 @@ class AbstractCheckbox extends AbstractComponent { this.inline = !!params.inline; this.labeled = undefined !== params.label; - this.labelAnimated = !!params.labelAnimated; } click() { diff --git a/snappymail/v/0.0.0/app/templates/Views/Admin/AdminLogin.html b/snappymail/v/0.0.0/app/templates/Views/Admin/AdminLogin.html index 7b3a51ab1..bf7f94225 100644 --- a/snappymail/v/0.0.0/app/templates/Views/Admin/AdminLogin.html +++ b/snappymail/v/0.0.0/app/templates/Views/Admin/AdminLogin.html @@ -10,8 +10,8 @@
    + data-bind="textInput: login, disable: submitRequest" + data-i18n="[placeholder]LOGIN/LABEL_LOGIN" /> šŸ‘¤ @@ -21,19 +21,20 @@
    - + data-bind="textInput: password, disable: submitRequest" + data-i18n="[placeholder]LOGIN/LABEL_PASSWORD" /> + šŸ”‘
    +
    + data-i18n="LOGIN/BUTTON_LOGIN">
    diff --git a/snappymail/v/0.0.0/app/templates/Views/Components/Checkbox.html b/snappymail/v/0.0.0/app/templates/Views/Components/Checkbox.html index 41a93e366..d07ccb4ae 100644 --- a/snappymail/v/0.0.0/app/templates/Views/Components/Checkbox.html +++ b/snappymail/v/0.0.0/app/templates/Views/Components/Checkbox.html @@ -1,6 +1,6 @@ - + diff --git a/snappymail/v/0.0.0/app/templates/Views/Components/CheckboxMaterialDesign.html b/snappymail/v/0.0.0/app/templates/Views/Components/CheckboxMaterialDesign.html index 59cc63d20..c1975b3de 100644 --- a/snappymail/v/0.0.0/app/templates/Views/Components/CheckboxMaterialDesign.html +++ b/snappymail/v/0.0.0/app/templates/Views/Components/CheckboxMaterialDesign.html @@ -5,6 +5,6 @@ 'box': animationBox, 'checkmark': animationCheckmark}">
  • - + diff --git a/snappymail/v/0.0.0/app/templates/Views/User/Login.html b/snappymail/v/0.0.0/app/templates/Views/User/Login.html index e423b1aa6..7723af6a4 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/Login.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/Login.html @@ -6,18 +6,14 @@
    - @@ -41,9 +37,9 @@
    - +
    @@ -57,7 +53,6 @@ name: 'CheckboxSimple', params: { label: 'LOGIN/LABEL_SIGN_ME', - labelAnimated: true, value: signMe } }">
    diff --git a/snappymail/v/0.0.0/app/templates/Views/User/SettingsGeneral.html b/snappymail/v/0.0.0/app/templates/Views/User/SettingsGeneral.html index b18de2cd0..3842107bb 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/SettingsGeneral.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/SettingsGeneral.html @@ -1,8 +1,6 @@
    -
    - -
    +
    @@ -108,9 +106,7 @@
    -
    - -
    +
    From e112d1fd7088a352c32b5dacae6db93051f13d58 Mon Sep 17 00:00:00 2001 From: djmaze Date: Wed, 14 Apr 2021 14:30:42 +0200 Subject: [PATCH 5/5] Change and document plugins hook system --- plugins/README.md | 345 ++++++++++++++++++ plugins/auto-domain-grab/index.php | 4 +- plugins/black-list/index.php | 2 +- plugins/change-smtp-ehlo-message/index.php | 2 +- plugins/custom-auth-example/index.php | 4 +- plugins/custom-login-mapping/index.php | 4 +- plugins/override-smtp-credentials/index.php | 2 +- plugins/proxyauth-login-example/index.php | 2 +- plugins/white-list/index.php | 2 +- .../0.0.0/app/libraries/RainLoop/Actions.php | 14 +- .../libraries/RainLoop/Actions/Folders.php | 24 +- .../libraries/RainLoop/Enumerations/Capa.php | 1 - .../app/libraries/RainLoop/Model/Account.php | 66 ++-- .../libraries/RainLoop/Plugins/Manager.php | 2 - 14 files changed, 395 insertions(+), 79 deletions(-) create mode 100644 plugins/README.md diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 000000000..177139cba --- /dev/null +++ b/plugins/README.md @@ -0,0 +1,345 @@ + +class Plugin extends \RainLoop\Plugins\AbstractPlugin + +# Hooks + +$Plugin->addHook('hook.name', 'functionName'); + +## Login + +### login.credentials.step-1 + params: + string &$sEmail + +### login.credentials.step-2 + params: + string &$sEmail + string &$sPassword + +### login.credentials + params: + string &$sEmail + string &$sLogin + string &$sPassword + +### login.success + params: + \RainLoop\Model\Account $oAccount + +## IMAP + +### imap.credentials + params: + \RainLoop\Model\Account $oAccount + array &$aCredentials + +### imap.before-connect + params: + \RainLoop\Model\Account $oAccount + \MailSo\Mail\MailClient $oMailClient + array $aCredentials + +### imap.after-connect + params: + \RainLoop\Model\Account $oAccount + \MailSo\Mail\MailClient $oMailClient + array $aCredentials + +### imap.before-login + params: + \RainLoop\Model\Account $oAccount + \MailSo\Mail\MailClient $oMailClient + array $aCredentials + +### imap.after-login + params: + \RainLoop\Model\Account $oAccount + \MailSo\Mail\MailClient $oMailClient + bool $bSuccess + array $aCredentials + +## Sieve + +### sieve.credentials + params: + \RainLoop\Model\Account $oAccount + array &$aCredentials + +### sieve.before-connect + params: + \RainLoop\Model\Account $oAccount + \MailSo\Sieve\ManageSieveClient $oSieveClient + array $aCredentials + +### sieve.after-connect + params: + \RainLoop\Model\Account $oAccount + \MailSo\Sieve\ManageSieveClient $oSieveClient + array $aCredentials + +### sieve.before-login + params: + \RainLoop\Model\Account $oAccount + \MailSo\Sieve\ManageSieveClient $oSieveClient + bool $bSuccess + array $aCredentials + +### sieve.after-login + params: + \RainLoop\Model\Account $oAccount + \MailSo\Sieve\ManageSieveClient $oSieveClient + array $aCredentials + +## SMTP + +### smtp.credentials + params: + \RainLoop\Model\Account $oAccount + array &$aCredentials + +### smtp.before-connect + params: + \RainLoop\Model\Account $oAccount + \MailSo\Smtp\SmtpClient $oSmtpClient + array $aCredentials + +### smtp.after-connect + params: + \RainLoop\Model\Account $oAccount + \MailSo\Smtp\SmtpClient $oSmtpClient + array $aCredentials + +### smtp.before-login + params: + \RainLoop\Model\Account $oAccount + \MailSo\Smtp\SmtpClient $oSmtpClient + array $aCredentials + +### smtp.after-login + params: + \RainLoop\Model\Account $oAccount + \MailSo\Smtp\SmtpClient $oSmtpClient + bool $bSuccess + array $aCredentials + +## Folders + +### filter.folders-post + params: + \RainLoop\Model\Account $oAccount + \MailSo\Mail\FolderCollection $oFolderCollection + +### filter.folders-complete + params: + \RainLoop\Model\Account $oAccount + \MailSo\Mail\FolderCollection $oFolderCollection + +### filter.folders-system-types + params: + \RainLoop\Model\Account $oAccount + array &$aList + +### filter.system-folders-names + params: + \RainLoop\Model\Account $oAccount + array &$aCache + +## Others + +### filter.account + params: + \RainLoop\Model\Account $oAccount + +### filter.action-params + params: + string $sMethodName + array &$aCurrentActionParams + +### filter.app-data + params: + bool $bAdmin + array &$aAppData + +### filter.application-config + params: + \RainLoop\Config\Application $oConfig + +### filter.build-message + params: + \MailSo\Mime\Message $oMessage + +### filter.build-read-receipt-message + params: + \MailSo\Mime\Message $oMessage + \RainLoop\Model\Account $oAccount + +### filter.domain + params: + \RainLoop\Model\Domain &$oDomain + +### filter.fabrica + params: + string $sName + mixed &$mResult + \RainLoop\Model\Account $oAccount + +### filter.http-paths + params: + array &$aPaths + +### filter.http-query + params: + string &$sQuery + +### filter.json-response + params: + string $sAction + array &$aResponseItem + +### filter.message-html' +### filter.message-plain + params: + \RainLoop\Model\Account $oAccount + \MailSo\Mime\Message $oMessage + string &$sTextConverted + +### filter.message-rcpt + params: + \RainLoop\Model\Account $oAccount + \MailSo\Mime\EmailCollection $oRcpt + +### filter.read-receipt-message-plain + params: + \RainLoop\Model\Account $oAccount + \MailSo\Mime\Message $oMessage + string &$sText + +### filter.result-message + params: + \MailSo\Mime\Message $oMessage + +### filter.save-message + params: + \MailSo\Mime\Message $oMessage + +### filter.send-message + params: + \MailSo\Mime\Message $oMessage + +### filter.send-message-stream + params: + \RainLoop\Model\Account $oAccount + resource &$rMessageStream + int &$iMessageStreamSize + +### filter.send-read-receipt-message + params: + \MailSo\Mime\Message $oMessage + \RainLoop\Model\Account $oAccount + +### filter.smtp-from + params: + \RainLoop\Model\Account $oAccount + \MailSo\Mime\Message $oMessage + string &$sFrom + +### filter.smtp-hidden-rcpt + params: + \RainLoop\Model\Account $oAccount + \MailSo\Mime\Message $oMessage + array &$aHiddenRcpt + +### filter.smtp-message-stream + params: + \RainLoop\Model\Account $oAccount + resource &$rMessageStream + int &$iMessageStreamSize + +### filter.upload-response + params: + array &$aResponseItem + +### json.action-post-call + params: + string $sAction + array &$aResponseItem + +### json.action-post-call + params: + string $sAction + array &$aResponseItem + +### json.action-pre-call + params: + string $sAction + +### json.action-pre-call + params: + string $sAction + +### json.suggestions-input-parameters + params: + string &$sQuery + int &$iLimit + \RainLoop\Model\Account $oAccount + +### json.suggestions-post + params: + array &$aResult + string $sQuery + \RainLoop\Model\Account $oAccount + int $iLimit + +### json.suggestions-pre + params: + array &$aResult + string $sQuery + \RainLoop\Model\Account $oAccount + int $iLimit + +### main.default-response + params: + string $sActionName + array &$aResponseItem + +### main.default-response + params: + string $sActionName + array &$aResponseItem + +### main.default-response + params: + string $sActionName + array &$aResponseItem + +### main.default-response-data + params: + string $sActionName + mixed &$mResult + +### main.default-response-data + params: + string $sActionName + mixed &$mResult + +### main.default-response-data + params: + string $sActionName + mixed &$mResult + +### main.default-response-error-data + params: + string $sActionName + int &$iErrorCode + string &$sErrorMessage + +### main.fabrica + params: + string $sName + mixed &$mResult + +### service.app-delay-start-begin + no params + +### service.app-delay-start-end + no params diff --git a/plugins/auto-domain-grab/index.php b/plugins/auto-domain-grab/index.php index 1fe9307b5..64a5f0ec5 100644 --- a/plugins/auto-domain-grab/index.php +++ b/plugins/auto-domain-grab/index.php @@ -22,8 +22,8 @@ class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin public function Init() : void { - $this->addHook('filter.smtp-credentials', 'FilterSmtpCredentials'); - $this->addHook('filter.imap-credentials', 'FilterImapCredentials'); + $this->addHook('smtp.credentials', 'FilterSmtpCredentials'); + $this->addHook('imap.credentials', 'FilterImapCredentials'); } /** diff --git a/plugins/black-list/index.php b/plugins/black-list/index.php index a820cabda..3b6aefd63 100644 --- a/plugins/black-list/index.php +++ b/plugins/black-list/index.php @@ -10,7 +10,7 @@ class BlackListPlugin extends \RainLoop\Plugins\AbstractPlugin public function Init() : void { - $this->addHook('filter.login-credentials', 'FilterLoginCredentials'); + $this->addHook('login.credentials', 'FilterLoginCredentials'); } /** diff --git a/plugins/change-smtp-ehlo-message/index.php b/plugins/change-smtp-ehlo-message/index.php index 8860653da..ad1b1d280 100644 --- a/plugins/change-smtp-ehlo-message/index.php +++ b/plugins/change-smtp-ehlo-message/index.php @@ -9,7 +9,7 @@ class ChangeSmtpEhloMessagePlugin extends \RainLoop\Plugins\AbstractPlugin public function Init() : void { - $this->addHook('filter.smtp-credentials', 'FilterSmtpCredentials'); + $this->addHook('smtp.credentials', 'FilterSmtpCredentials'); } /** diff --git a/plugins/custom-auth-example/index.php b/plugins/custom-auth-example/index.php index 59f2b0588..e68572c14 100644 --- a/plugins/custom-auth-example/index.php +++ b/plugins/custom-auth-example/index.php @@ -9,7 +9,7 @@ class CustomAuthExamplePlugin extends \RainLoop\Plugins\AbstractPlugin public function Init() : void { - $this->addHook('filter.login-credentials', 'FilterLoginДredentials'); + $this->addHook('login.credentials', 'FilterLoginCredentials'); } /** @@ -19,7 +19,7 @@ class CustomAuthExamplePlugin extends \RainLoop\Plugins\AbstractPlugin * * @throws \RainLoop\Exceptions\ClientException */ - public function FilterLoginДredentials(&$sEmail, &$sLogin, &$sPassword) + public function FilterLoginCredentials(&$sEmail, &$sLogin, &$sPassword) { // Your custom php logic // You may change login credentials diff --git a/plugins/custom-login-mapping/index.php b/plugins/custom-login-mapping/index.php index ac3d577da..3c78cd10f 100644 --- a/plugins/custom-login-mapping/index.php +++ b/plugins/custom-login-mapping/index.php @@ -10,7 +10,7 @@ class CustomLoginMappingPlugin extends \RainLoop\Plugins\AbstractPlugin public function Init() : void { - $this->addHook('filter.login-credentials', 'FilterLoginДredentials'); + $this->addHook('login.credentials', 'FilterLoginCredentials'); } /** @@ -20,7 +20,7 @@ class CustomLoginMappingPlugin extends \RainLoop\Plugins\AbstractPlugin * * @throws \RainLoop\Exceptions\ClientException */ - public function FilterLoginДredentials(&$sEmail, &$sLogin, &$sPassword) + public function FilterLoginCredentials(&$sEmail, &$sLogin, &$sPassword) { $sMapping = \trim($this->Config()->Get('plugin', 'mapping', '')); if (!empty($sMapping)) diff --git a/plugins/override-smtp-credentials/index.php b/plugins/override-smtp-credentials/index.php index 9b4732458..1b019e565 100644 --- a/plugins/override-smtp-credentials/index.php +++ b/plugins/override-smtp-credentials/index.php @@ -10,7 +10,7 @@ class OverrideSmtpCredentialsPlugin extends \RainLoop\Plugins\AbstractPlugin public function Init() : void { - $this->addHook('filter.smtp-credentials', 'FilterSmtpCredentials'); + $this->addHook('smtp.credentials', 'FilterSmtpCredentials'); } /** diff --git a/plugins/proxyauth-login-example/index.php b/plugins/proxyauth-login-example/index.php index caf010552..18c35ea33 100644 --- a/plugins/proxyauth-login-example/index.php +++ b/plugins/proxyauth-login-example/index.php @@ -9,7 +9,7 @@ class ProxyauthLoginExamplePlugin extends \RainLoop\Plugins\AbstractPlugin public function Init() : void { - $this->addHook('event.login-post-login-provide', 'EventLoginPostLoginProvide'); + $this->addHook('login.success', 'EventLoginPostLoginProvide'); } /** diff --git a/plugins/white-list/index.php b/plugins/white-list/index.php index fd01124ee..b2e4bebd8 100644 --- a/plugins/white-list/index.php +++ b/plugins/white-list/index.php @@ -10,7 +10,7 @@ class WhiteListPlugin extends \RainLoop\Plugins\AbstractPlugin public function Init() : void { - $this->addHook('filter.login-credentials', 'FilterLoginCredentials'); + $this->addHook('login.credentials', 'FilterLoginCredentials'); } /** diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php index c659c27e0..bc724ab4e 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -871,7 +871,7 @@ class Actions } } - public function LoginProvide(string $sEmail, string $sLogin, string $sPassword, string $sSignMeToken = '', string $sClientCert = '', bool $bThrowProvideException = false): ?Model\Account + protected function LoginProvide(string $sEmail, string $sLogin, string $sPassword, string $sSignMeToken = '', string $sClientCert = '', bool $bThrowProvideException = false): ?Model\Account { $oAccount = null; if (0 < \strlen($sEmail) && 0 < \strlen($sLogin) && 0 < \strlen($sPassword)) { @@ -879,7 +879,7 @@ class Actions if ($oDomain) { if ($oDomain->ValidateWhiteList($sEmail, $sLogin)) { $oAccount = new Model\Account($sEmail, $sLogin, $sPassword, $oDomain, $sSignMeToken, '', '', $sClientCert); - $this->Plugins()->RunHook('filter.acount', array($oAccount)); + $this->Plugins()->RunHook('filter.account', array($oAccount)); if ($bThrowProvideException && !$oAccount) { throw new Exceptions\ClientException(Notifications::AuthError); @@ -1419,7 +1419,7 @@ class Actions { $sInputEmail = $sEmail; - $this->Plugins()->RunHook('filter.login-credentials.step-1', array(&$sEmail, &$sPassword)); + $this->Plugins()->RunHook('login.credentials.step-1', array(&$sEmail)); $sEmail = \MailSo\Base\Utils::Trim($sEmail); if ($this->Config()->Get('login', 'login_lowercase', true)) { @@ -1486,7 +1486,7 @@ class Actions } } - $this->Plugins()->RunHook('filter.login-credentials.step-2', array(&$sEmail, &$sPassword)); + $this->Plugins()->RunHook('login.credentials.step-2', array(&$sEmail, &$sPassword)); if (false === \strpos($sEmail, '@') || 0 === \strlen($sPassword)) { $this->loginErrorDelay(); @@ -1501,12 +1501,10 @@ class Actions $sLogin = \MailSo\Base\Utils::StrToLowerIfAscii($sLogin); } - $this->Plugins()->RunHook('filter.login-credentials', array(&$sEmail, &$sLogin, &$sPassword)); + $this->Plugins()->RunHook('login.credentials', array(&$sEmail, &$sLogin, &$sPassword)); $this->Logger()->AddSecret($sPassword); - $this->Plugins()->RunHook('event.login-pre-login-provide', array()); - $oAccount = null; $sClientCert = \trim($this->Config()->Get('ssl', 'client_cert', '')); try { @@ -1516,7 +1514,7 @@ class Actions throw new Exceptions\ClientException(Notifications::AuthError); } - $this->Plugins()->RunHook('event.login-post-login-provide', array($oAccount)); + $this->Plugins()->RunHook('login.success', array($oAccount)); } catch (\Throwable $oException) { $this->loginErrorDelay(); $this->LoggerAuthHelper($oAccount, $this->getAdditionalLogParamsByUserLogin($sInputEmail)); diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php index 9ccc85810..366f1303b 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php @@ -14,28 +14,22 @@ trait Folders { $oAccount = $this->initMailClientConnection(); - $oFolderCollection = null; - $this->Plugins()->RunHook('filter.folders-before', array($oAccount, $oFolderCollection)); - $HideUnsubscribed = $this->Config()->Get('labs', 'use_imap_list_subscribe', true); $oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount); if ($oSettingsLocal instanceof \RainLoop\Settings) { $HideUnsubscribed = (bool) $oSettingsLocal->GetConf('HideUnsubscribed', $HideUnsubscribed); } - if (null === $oFolderCollection) - { - $oFolderCollection = $this->MailClient()->Folders('', - '*', - $HideUnsubscribed, - (int) $this->Config()->Get('labs', 'imap_folder_list_limit', 200) - ); - } - - $this->Plugins()->RunHook('filter.folders-post', array($oAccount, $oFolderCollection)); + $oFolderCollection = $this->MailClient()->Folders('', + '*', + $HideUnsubscribed, + (int) $this->Config()->Get('labs', 'imap_folder_list_limit', 200) + ); if ($oFolderCollection instanceof \MailSo\Mail\FolderCollection) { + $this->Plugins()->RunHook('filter.folders-post', array($oAccount, $oFolderCollection)); + $aFolders = $oFolderCollection->getArrayCopy(); foreach ($aFolders as $i => $oFolder) { if (!$oFolder->IsSelectable()) { @@ -158,9 +152,9 @@ trait Folders { $oFolderCollection->FoldersHash = \md5(\implode("\x0", $this->recFoldersNames($oFolderCollection))); } - } - $this->Plugins()->RunHook('filter.folders-complete', array($oAccount, $oFolderCollection)); + $this->Plugins()->RunHook('filter.folders-complete', array($oAccount, $oFolderCollection)); + } return $this->DefaultResponse(__FUNCTION__, $oFolderCollection); } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Enumerations/Capa.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Enumerations/Capa.php index 8cb98bc1d..bbb93b4ef 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Enumerations/Capa.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Enumerations/Capa.php @@ -4,7 +4,6 @@ namespace RainLoop\Enumerations; class Capa { - const PREM = 'PREM'; const OPEN_PGP = 'OPEN_PGP'; const PREFETCH = 'PREFETCH'; const THEMES = 'THEMES'; diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php index 8e3257a91..1a02b996f 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php @@ -285,21 +285,18 @@ class Account $oPlugins->RunHook('filter.imap-credentials', array($this, &$aImapCredentials)); - $oPlugins->RunHook('event.imap-pre-connect', array($this, $aImapCredentials['UseConnect'], $aImapCredentials)); - - if ($aImapCredentials['UseConnect']) - { + $oPlugins->RunHook('imap.before-connect', array($this, $oMailClient, $aImapCredentials)); + if ($aImapCredentials['UseConnect']) { $oMailClient ->Connect($aImapCredentials['Host'], $aImapCredentials['Port'], $aImapCredentials['Secure'], $aImapCredentials['VerifySsl'], $aImapCredentials['AllowSelfSigned'], $aImapCredentials['ClientCert']); } + $oPlugins->RunHook('imap.after-connect', array($this, $oMailClient, $aImapCredentials)); - $oPlugins->RunHook('event.imap-pre-login', array($this, $aImapCredentials['UseAuth'], $aImapCredentials)); - - if ($aImapCredentials['UseAuth']) - { + $oPlugins->RunHook('imap.before-login', array($this, $oMailClient, $aImapCredentials)); + if ($aImapCredentials['UseAuth']) { if (0 < \strlen($aImapCredentials['ProxyAuthUser']) && 0 < \strlen($aImapCredentials['ProxyAuthPassword'])) { @@ -315,8 +312,7 @@ class Account $bLogin = true; } - - $oPlugins->RunHook('event.imap-post-login', array($this, $aImapCredentials['UseAuth'], $bLogin, $aImapCredentials)); + $oPlugins->RunHook('imap.after-login', array($this, $oMailClient, $bLogin, $aImapCredentials)); return $bLogin; } @@ -326,7 +322,7 @@ class Account $bLogin = false; $aSmtpCredentials = array( - 'UseConnect' => true, + 'UseConnect' => !$bUsePhpMail, 'UseAuth' => $this->DomainOutAuth(), 'UsePhpMail' => $bUsePhpMail, 'Ehlo' => \MailSo\Smtp\SmtpClient::EhloHelper(), @@ -343,23 +339,20 @@ class Account 'UseAuthCramMd5IfSupported' => !!$oConfig->Get('labs', 'smtp_use_auth_cram_md5', true) ); - $oPlugins->RunHook('filter.smtp-credentials', array($this, &$aSmtpCredentials)); + $oPlugins->RunHook('smtp.credentials', array($this, &$aSmtpCredentials)); $bUsePhpMail = $aSmtpCredentials['UsePhpMail']; - $oPlugins->RunHook('event.smtp-pre-connect', array($this, $aSmtpCredentials['UseConnect'], $aSmtpCredentials)); - - if ($aSmtpCredentials['UseConnect'] && !$aSmtpCredentials['UsePhpMail'] && $oSmtpClient) - { + $oPlugins->RunHook('smtp.before-connect', array($this, $oSmtpClient, $aSmtpCredentials)); + if ($aSmtpCredentials['UseConnect']) { $oSmtpClient->Connect($aSmtpCredentials['Host'], $aSmtpCredentials['Port'], $aSmtpCredentials['Secure'], $aSmtpCredentials['VerifySsl'], $aSmtpCredentials['AllowSelfSigned'], '', $aSmtpCredentials['Ehlo'] ); } + $oPlugins->RunHook('smtp.after-connect', array($this, $oSmtpClient, $aSmtpCredentials)); - $oPlugins->RunHook('event.smtp-post-connect', array($this, $aSmtpCredentials['UseConnect'], $aSmtpCredentials)); - $oPlugins->RunHook('event.smtp-pre-login', array($this, $aSmtpCredentials['UseAuth'], $aSmtpCredentials)); - + $oPlugins->RunHook('smtp.before-login', array($this, $oSmtpClient, $aSmtpCredentials)); if ($aSmtpCredentials['UseAuth'] && !$aSmtpCredentials['UsePhpMail'] && $oSmtpClient) { $oSmtpClient->Login($aSmtpCredentials['Login'], $aSmtpCredentials['Password'], @@ -367,8 +360,7 @@ class Account $bLogin = true; } - - $oPlugins->RunHook('event.smtp-post-login', array($this, $aSmtpCredentials['UseAuth'], $bLogin, $aSmtpCredentials)); + $oPlugins->RunHook('smtp.after-login', array($this, $oSmtpClient, $bLogin, $aSmtpCredentials)); return $bLogin; } @@ -390,34 +382,24 @@ class Account 'InitialAuthPlain' => !!$oConfig->Get('ssl', 'sieve_auth_plain_initial', true) ); - $oPlugins->RunHook('filter.sieve-credentials', array($this, &$aSieveCredentials)); + $oPlugins->RunHook('sieve.credentials', array($this, &$aSieveCredentials)); - $oPlugins->RunHook('event.sieve-pre-connect', array($this, $aSieveCredentials['UseConnect'], $aSieveCredentials)); + $oSieveClient->__USE_INITIAL_AUTH_PLAIN_COMMAND = $aSieveCredentials['InitialAuthPlain']; - if ($oSieveClient) - { - $oSieveClient->__USE_INITIAL_AUTH_PLAIN_COMMAND = $aSieveCredentials['InitialAuthPlain']; - - if ($aSieveCredentials['UseConnect']) - { - $oSieveClient->Connect($aSieveCredentials['Host'], $aSieveCredentials['Port'], - $aSieveCredentials['Secure'], $aSieveCredentials['VerifySsl'], $aSieveCredentials['AllowSelfSigned'] - ); - } + $oPlugins->RunHook('sieve.before-connect', array($this, $oSieveClient, $aSieveCredentials)); + if ($aSieveCredentials['UseConnect']) { + $oSieveClient->Connect($aSieveCredentials['Host'], $aSieveCredentials['Port'], + $aSieveCredentials['Secure'], $aSieveCredentials['VerifySsl'], $aSieveCredentials['AllowSelfSigned'] + ); } + $oPlugins->RunHook('sieve.after-connect', array($this, $oSieveClient, $aSieveCredentials)); - $oPlugins->RunHook('event.sieve-post-connect', array($this, $aSieveCredentials['UseConnect'], $aSieveCredentials)); - - $oPlugins->RunHook('event.sieve-pre-login', array($this, $aSieveCredentials['UseAuth'], $aSieveCredentials)); - - if ($aSieveCredentials['UseAuth']) - { + $oPlugins->RunHook('event.sieve-pre-login', array($this, $oSieveClient, $aSieveCredentials)); + if ($aSieveCredentials['UseAuth']) { $oSieveClient->Login($aSieveCredentials['Login'], $aSieveCredentials['Password']); - $bLogin = true; } - - $oPlugins->RunHook('event.sieve-post-login', array($this, $aSieveCredentials['UseAuth'], $bLogin, $aSieveCredentials)); + $oPlugins->RunHook('sieve.after-login', array($this, $oSieveClient, $bLogin, $aSieveCredentials)); return $bLogin; } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Plugins/Manager.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Plugins/Manager.php index 2326c4c1d..0091b3f99 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Plugins/Manager.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Plugins/Manager.php @@ -57,8 +57,6 @@ class Manager $oConfig->Save(); } } - - $this->RunHook('api.bootstrap.plugins'); } }