mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-08 22:18:28 +03:00
Merge branch 'master' into plugin-2fa
This commit is contained in:
commit
c30de83da9
32 changed files with 1079 additions and 866 deletions
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ class AbstractCheckbox extends AbstractComponent {
|
|||
this.inline = !!params.inline;
|
||||
|
||||
this.labeled = undefined !== params.label;
|
||||
this.labelAnimated = !!params.labelAnimated;
|
||||
}
|
||||
|
||||
click() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
345
plugins/README.md
Normal file
345
plugins/README.md
Normal file
|
|
@ -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
|
||||
|
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ namespace RainLoop\Enumerations;
|
|||
|
||||
class Capa
|
||||
{
|
||||
const PREM = 'PREM';
|
||||
const OPEN_PGP = 'OPEN_PGP';
|
||||
const PREFETCH = 'PREFETCH';
|
||||
const THEMES = 'THEMES';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,8 +57,6 @@ class Manager
|
|||
$oConfig->Save();
|
||||
}
|
||||
}
|
||||
|
||||
$this->RunHook('api.bootstrap.plugins');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,484 @@
|
|||
<?php
|
||||
|
||||
namespace RainLoop\Providers\AddressBook;
|
||||
|
||||
use \SnappyMail\DAV\Client as DAVClient;
|
||||
|
||||
trait CardDAV
|
||||
{
|
||||
protected function prepareDavSyncData(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])
|
||||
&& !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']);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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,490 +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'] || !$aItem['{dav:}resourcetype']->is('{DAV:}collection')))
|
||||
{
|
||||
$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))
|
||||
{
|
||||
$oResourceType = isset($aItem['{DAV:}resourcetype']) ? $aItem['{DAV:}resourcetype'] : null;
|
||||
/* @var $oResourceType \Sabre\DAV\Property\ResourceType */
|
||||
if ($oResourceType && $oResourceType->is('{DAV:}collection'))
|
||||
{
|
||||
$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']))
|
||||
{
|
||||
$oResourceType = $aItem['{DAV:}resourcetype'];
|
||||
/* @var $oResourceType \Sabre\DAV\Property\ResourceType */
|
||||
|
||||
if ($oResourceType && $oResourceType->is('{DAV:}collection'))
|
||||
{
|
||||
if ($oResourceType->is('{urn:ietf:params:xml:ns:carddav}addressbook'))
|
||||
{
|
||||
$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']))
|
||||
{
|
||||
$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'))
|
||||
{
|
||||
$bGood = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($bGood)
|
||||
{
|
||||
$oClient->__UrlPath__ = $sPath;
|
||||
}
|
||||
|
||||
return $bGood;
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
|
||||
public 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();
|
||||
|
|
@ -607,7 +124,7 @@ class PdoAddressBook
|
|||
|
||||
$sPath = $oClient->__UrlPath__;
|
||||
|
||||
$aRemoteSyncData = $this->prepearRemoteSyncData($oClient, $sPath);
|
||||
$aRemoteSyncData = $this->prepareDavSyncData($oClient, $sPath);
|
||||
if (false === $aRemoteSyncData)
|
||||
{
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -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 .= ' </d:prop>' . "\n";
|
||||
$body .= '</d:propfind>';
|
||||
|
||||
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,22 @@ 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 = $ns ? "{$ns}:" : '';
|
||||
// $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 +209,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 +221,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 +235,4 @@ class Client
|
|||
}
|
||||
return $propList;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
<div class="input-append">
|
||||
<input required="" type="text" class="input-block-level inputLogin"
|
||||
autofocus="" autocomplete="username" autocorrect="off" autocapitalize="off" spellcheck="false"
|
||||
data-i18n="[placeholder]LOGIN/LABEL_LOGIN"
|
||||
data-bind="textInput: login, disable: submitRequest" />
|
||||
data-bind="textInput: login, disable: submitRequest"
|
||||
data-i18n="[placeholder]LOGIN/LABEL_LOGIN" />
|
||||
<span class="add-on">
|
||||
<i class="fontastic">👤</i>
|
||||
</span>
|
||||
|
|
@ -21,19 +21,20 @@
|
|||
<div class="input-append">
|
||||
<input required="" type="password" class="input-block-level inputPassword"
|
||||
autocomplete="current-password" autocorrect="off" autocapitalize="off" spellcheck="false"
|
||||
data-i18n="[placeholder]LOGIN/LABEL_PASSWORD"
|
||||
data-bind="textInput: password, disable: submitRequest" />
|
||||
<span class="add-on" tabindex="-1" data-bind="command: submitCommand" data-i18n="[title]LOGIN/BUTTON_LOGIN">
|
||||
data-bind="textInput: password, disable: submitRequest"
|
||||
data-i18n="[placeholder]LOGIN/LABEL_PASSWORD" />
|
||||
<span class="add-on" tabindex="-1"
|
||||
data-bind="command: submitCommand" data-i18n="[title]LOGIN/BUTTON_LOGIN">
|
||||
<i class="fontastic" data-bind="visible: '' === password()">🔑</i>
|
||||
<button type="submit" class="btn-submit-icon-wrp login-submit-icon fontastic" data-bind="visible: '' !== password()">❯</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="plugin-Login-BottomControlGroup"></div>
|
||||
<div class="controls" data-bind="hidden: hideSubmitButton">
|
||||
<button type="submit" class="btn btn-large btn-block buttonLogin"
|
||||
data-bind="command: submitCommand"
|
||||
data-i18n="LOGIN/BUTTON_LOGIN">
|
||||
</button>
|
||||
data-i18n="LOGIN/BUTTON_LOGIN"></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<span class="e-component e-checkbox inline" tabindex="0" data-bind="click: click, onSpace: click, css: { 'disabled': disable() || !enable() }">
|
||||
<i role="checkbox" class="e-checkbox-icon fontastic" data-bind="text: value() ? '☑' : '☐'"></i>
|
||||
<!-- ko if: labeled -->
|
||||
<span class="sub-label" data-bind="attr: {'data-i18n': label}, css: {'i18n-animation': labelAnimated}"></span>
|
||||
<span class="sub-label" data-bind="attr: {'data-i18n': label}"></span>
|
||||
<!-- /ko -->
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@
|
|||
'box': animationBox, 'checkmark': animationCheckmark}"></div>
|
||||
</div>
|
||||
<!-- ko if: labeled -->
|
||||
<span class="sub-label" data-bind="attr: {'data-i18n': label}, css: {'i18n-animation': labelAnimated}"></span>
|
||||
<span class="sub-label" data-bind="attr: {'data-i18n': label}"></span>
|
||||
<!-- /ko -->
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -6,18 +6,14 @@
|
|||
<div class="alert" data-bind="hidden: !submitError()" hidden="">
|
||||
<button type="button" class="close" data-bind="click: function () { submitError('') }">×</button>
|
||||
<span data-bind="text: submitError"></span>
|
||||
<div data-bind="visible: '' !== submitErrorAddidional()">
|
||||
<br />
|
||||
<span data-bind="text: submitErrorAddidional"></span>
|
||||
</div>
|
||||
<p data-bind="visible: '' !== submitErrorAddidional(), text: submitErrorAddidional"></p>
|
||||
</div>
|
||||
<div class="wrapper-parent">
|
||||
<form class="wrapper submitting-pane loginForm" action="#/"
|
||||
data-bind="submit: submitForm, css: {'errorAnimated': formError, 'submitting': submitRequest()}">
|
||||
<div class="controls" data-bind="css: {'error': emailError}">
|
||||
<div class="input-append">
|
||||
<input required="" type="text" class="input-block-level inputEmail"
|
||||
pattern="[^@\s]+(@[^\s]+)?" inputmode="email"
|
||||
<input required="" type="text" class="input-block-level inputEmail" pattern="[^@\s]+(@[^\s]+)?" inputmode="email"
|
||||
autofocus="" autocomplete="email" autocorrect="off" autocapitalize="off" spellcheck="false"
|
||||
data-bind="textInput: email, disable: submitRequest"
|
||||
data-i18n="[placeholder]GLOBAL/EMAIL" />
|
||||
|
|
@ -41,9 +37,9 @@
|
|||
</div>
|
||||
<div id="plugin-Login-BottomControlGroup"></div>
|
||||
<div class="controls" data-bind="hidden: hideSubmitButton">
|
||||
<button type="submit" class="btn btn-large btn-block buttonLogin" data-bind="command: submitCommand">
|
||||
<span class="i18n-animation" data-i18n="LOGIN/BUTTON_SIGN_IN"></span>
|
||||
</button>
|
||||
<button type="submit" class="btn btn-large btn-block buttonLogin"
|
||||
data-bind="command: submitCommand"
|
||||
data-i18n="LOGIN/BUTTON_SIGN_IN"></button>
|
||||
</div>
|
||||
<div class="controls clearfix" style="margin-bottom: 10px">
|
||||
<div class="pull-right language-buttons">
|
||||
|
|
@ -57,7 +53,6 @@
|
|||
name: 'CheckboxSimple',
|
||||
params: {
|
||||
label: 'LOGIN/LABEL_SIGN_ME',
|
||||
labelAnimated: true,
|
||||
value: signMe
|
||||
}
|
||||
}"></div>
|
||||
|
|
|
|||
|
|
@ -278,15 +278,13 @@
|
|||
<div class="attachmentIconParent" data-bind="css: { 'hasPreview': hasPreview(), 'hasPreplay': hasPreplay(), 'isImage': isImage() }">
|
||||
<div class="hidePreview">
|
||||
<div class="iconMain">
|
||||
<i class="attachmentIcon attachmentMainIcon" data-bind="css: iconClass()"></i>
|
||||
<i class="attachmentIconText attachmentMainIconText" data-bind="text: iconText()"></i>
|
||||
<i class="attachmentIcon" data-bind="css: iconClass()"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="showPreview">
|
||||
<a data-bind="click: $root.attachmentPreview, css: {'attachmentImagePreview': isImage(), 'visible': !isLinked}, attr: { 'title': fileName, 'href': linkPreviewMain() }" target="_blank">
|
||||
<div class="iconMain">
|
||||
<i class="attachmentIcon attachmentMainIcon" data-bind="css: iconClass()"></i>
|
||||
<i class="attachmentIconText attachmentMainIconText" data-bind="text: iconText()"></i>
|
||||
<i class="attachmentIcon" data-bind="css: iconClass()"></i>
|
||||
</div>
|
||||
<div class="iconBG" data-bind="attr: { 'style': linkThumbnailPreviewStyle() }"></div>
|
||||
<div class="iconPreview attachmentIcon fontastic show-hover">👁</div>
|
||||
|
|
@ -294,7 +292,7 @@
|
|||
</div>
|
||||
<div class="showPreplay">
|
||||
<div class="iconMain">
|
||||
<i class="attachmentIcon attachmentMainIcon" data-bind="css: iconClass()"></i>
|
||||
<i class="attachmentIcon" data-bind="css: iconClass()"></i>
|
||||
</div>
|
||||
<div class="iconPreview attachmentIcon fontastic show-hover">▶</div>
|
||||
</div>
|
||||
|
|
@ -316,8 +314,8 @@
|
|||
|
||||
<span data-bind="visible: downloadAsZipAllowed">
|
||||
<i class="fontastic iconcolor-red" data-bind="visible: downloadAsZipError">✖</i>
|
||||
<i class="icon-file-zip" data-bind="visible: !downloadAsZipError(),
|
||||
css: {'icon-file-zip': !downloadAsZipLoading(), 'icon-spinner': downloadAsZipLoading()}"></i>
|
||||
<i class="icon-file-archive" data-bind="visible: !downloadAsZipError(),
|
||||
css: {'icon-file-archive': !downloadAsZipLoading(), 'icon-spinner': downloadAsZipLoading()}"></i>
|
||||
<span class="g-ui-link" data-bind="click: downloadAsZip"
|
||||
data-i18n="MESSAGE/LINK_DOWNLOAD_AS_ZIP"></span>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -171,8 +171,7 @@
|
|||
<li class="attachmentItem" data-bind="attr: { 'title': title }, css: { 'waiting': waiting, 'error': '' !== error() }">
|
||||
<div class="attachmentIconParent">
|
||||
<div class="iconMain">
|
||||
<i class="attachmentIcon attachmentMainIcon" data-bind="css: iconClass(), visible: !uploading() || 0 === progress()"></i>
|
||||
<i class="attachmentIconText attachmentMainIconText" data-bind="text: iconText(), visible: !uploading() || 0 === progress()"></i>
|
||||
<i class="attachmentIcon" data-bind="css: iconClass(), visible: !uploading() || 0 === progress()"></i>
|
||||
</div>
|
||||
<div class="iconProgress" data-bind="attr: { 'style': progressStyle }, visible: uploading"></div>
|
||||
<div class="iconBG" data-bind="text: progressText, visible: uploading"></div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<div class="b-settings-general g-ui-user-select-none">
|
||||
<div class="form-horizontal">
|
||||
<div class="legend">
|
||||
<span class="i18n-animation" data-i18n="SETTINGS_GENERAL/LEGEND_GENERAL"></span>
|
||||
</div>
|
||||
<div class="legend" data-i18n="SETTINGS_GENERAL/LEGEND_GENERAL"></div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" data-i18n="SETTINGS_GENERAL/LABEL_IDENTITY"></label>
|
||||
<div class="controls">
|
||||
|
|
@ -108,9 +106,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-horizontal">
|
||||
<div class="legend">
|
||||
<span class="i18n-animation" data-i18n="SETTINGS_GENERAL/LABEL_NOTIFICATIONS"></span>
|
||||
</div>
|
||||
<div class="legend" data-i18n="SETTINGS_GENERAL/LABEL_NOTIFICATIONS"></div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
</div>
|
||||
<a class="btn button-back" data-bind="click: backToMailBoxClick">
|
||||
<i class="fontastic">⬅</i>
|
||||
<span class="i18n-animation" data-i18n="GLOBAL/BACK"></span>
|
||||
<span data-i18n="GLOBAL/BACK"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
10
vendors/fontastic/styles.css
vendored
10
vendors/fontastic/styles.css
vendored
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue